This R script is used to validate the points in the ReSurvey database using RS indicators (NDVI, NDMI, canopy height).

Load libraries

library(tidyverse)
library(here)
library(gridExtra)
library(readxl)
library(scales)
library(sf)
library(rnaturalearth)
library(dtplyr)
library(lme4)
library(lmerTest)
library(car)
library(ggeffects)
library(party)
library(partykit)
library(strucchange)
library(ggparty)
library(caret)
library(moreparty)
library(randomForest)

Define printall function

printall <- function(tibble) {
  print(tibble, width = Inf)
  }

Load geom_flat_violin plot

source("https://gist.githubusercontent.com/benmarwick/2a1bb0133ff568cbe28d/raw/fb53bd97121f7f9ce947837ef1a4c65a73bffb3f/geom_flat_violin.R")

Read ReSurvey data with RS indicators

db_resurv_RS<-read_tsv(
  here("data", "clean", "db_resurv_RS_20250505.csv"),
  col_types = cols(
    # Dynamically specify EUNIS columns as character
    .default = col_guess(),  # Default guessing for other columns
    EUNISa = col_character(),
    EUNISb = col_character(),
    EUNISc = col_character(),
    EUNISd = col_character(),
    EUNISa_1 = col_character(),
    EUNISa_2 = col_character(),
    EUNISa_3 = col_character(),
    EUNISa_4 = col_character(),
    EUNISb_1 = col_character(),
    EUNISb_2 = col_character(),
    EUNISb_3 = col_character(),
    EUNISb_4 = col_character(),
    EUNISc_1 = col_character(),
    EUNISc_2 = col_character(),
    EUNISc_3 = col_character(),
    EUNISc_4 = col_character(),
    EUNISd_1 = col_character(),
    EUNISd_2 = col_character(),
    EUNISd_3 = col_character(),
    EUNISd_4 = col_character(),
    EUNISa_1_descr = col_character(),
    EUNISb_1_descr = col_character(),
    EUNISc_1_descr = col_character(),
    EUNISd_1_descr = col_character(),
    EUNIS_assignation = col_character(),
    EUNISa_2_descr = col_character(),
    EUNISa_3_descr = col_character(),
    EUNISa_4_descr = col_character(),
    EUNISb_2_descr = col_character(),
    EUNISb_3_descr = col_character(),
    EUNISb_4_descr = col_character(),
    EUNISc_2_descr = col_character(),
    EUNISc_3_descr = col_character(),
    EUNISc_4_descr = col_character(),
    EUNISd_2_descr = col_character(),
    EUNISd_3_descr = col_character(),
    EUNISd_4_descr = col_character()
    )
  )

No parsing issues!

Some data managenemt

Several EUNIS level 1 assigned

Number of rows where there is more than one EUNIS 1 assigned, and they are different among them. See what to do with these later! So far I take EUNISa_1.

nrow(db_resurv_RS %>% 
       # Rows with more than one EUNIS 1 assigned
       filter(!is.na(EUNISb_1)) %>% 
       filter(EUNISa_1!=EUNISb_1 | EUNISb_1 != EUNISc_1 | EUNISa_1 != EUNISc_1))
[1] 102

See “confusions”:

db_resurv_RS %>% 
  # Rows with more than one EUNIS 1 assigned
  filter(!is.na(EUNISb_1)) %>% 
  filter(EUNISa_1!=EUNISb_1 | EUNISb_1 != EUNISc_1 | EUNISa_1 != EUNISc_1) %>%
  distinct(EUNISa_1, EUNISb_1, EUNISc_1, EUNISd_1)

Define “confusion” columns:

db_resurv_RS <- db_resurv_RS %>%
  mutate(EUNIS1_conf_type = case_when(
    EUNISa_1 == "R" & EUNISb_1 == "S" ~ "R/S",
    EUNISa_1 == "S" & EUNISb_1 == "T" ~ "S/T",
    EUNISa_1 == "R" & EUNISb_1 == "R" & EUNISc_1 == "S" ~ "R/S",
    EUNISa_1 == "R" & EUNISb_1 == "R" & EUNISc_1 == "S" & EUNISd_1 == "S" ~ "R/S",
    EUNISa_1 == "P" & EUNISb_1 == "Q" ~ "P/Q",
    TRUE ~ NA_character_),
    EUNIS1_conf = !is.na(EUNIS1_conf_type))

Tibble with selected columns

db_resurv_RS_short <- db_resurv_RS %>%
  select(PlotObservationID, Country, RS_CODE, `ReSurvey site`, `ReSurvey plot`,
         `Manipulate (y/n)`, `Type of manipulation`, Lon_updated, Lat_updated,
         `Location method`, `Location uncertainty (m)`, EUNISa_1,
         EUNISa_1_descr, EUNISa_2, EUNISa_2_descr, EUNISa_3, EUNISa_3_descr,
         EUNISa_4, EUNISa_4_descr, EUNIS1_conf, EUNIS1_conf_type,
         date, year, biogeo, unit, year_RS, Lon_RS, Lat_RS, 
         starts_with("NDVI"), starts_with("NDMI"), starts_with("NDWI"),
         starts_with("EVI"), starts_with("SAVI"), canopy_height,
         SOS_DOY, SOS_date, NDVI_at_SOS, Peak_DOY, Peak_date, NDVI_at_Peak,
         EOS_DOY, EOS_date, NDVI_at_EOS, Season_Length,
         S2_data, Landsat_data, CH_data, S2_phen_data)

TO-DO: Missing data checks

Do when all RS data is ready!

Flag when year is different between RS data and ReSurvey db

db_resurv_RS_short <- db_resurv_RS_short %>%
  mutate(year_diff = year != year_RS)
db_resurv_RS_short %>% count(year_diff)

2 with different year. RS indices would need to be calculated again for those.

Flag when coordinates are different between RS data and ReSurvey db

db_resurv_RS_short <- db_resurv_RS_short %>%
  mutate(Lon_diff = case_when(Lon_updated == Lon_RS ~ "NO",
                              # Sometimes they are only slighly different
                              abs(Lon_updated - Lon_RS) < 0.01 ~ "SMALL",
                              is.na(Lon_updated) | is.na(Lon_RS) ~ NA,
                              TRUE ~ "LARGE"),
         Lat_diff = case_when(Lat_updated == Lat_RS ~ "NO",
                              # Sometimes they are only slighly different
                              abs(Lat_updated - Lat_RS) < 0.01 ~ "SMALL",
                              is.na(Lat_updated) | is.na(Lat_RS) ~ NA,
                              TRUE ~ "LARGE"))
db_resurv_RS_short %>% count(Lon_diff)
db_resurv_RS_short %>% count(Lat_diff)

Very few with large differences (2 for longitude, 6 for latitude). RS indices would need to be calculated again for those.

If year_diff is TRUE or Lon_diff is LARGE or Lat_diff is LARGE –> RS indices need to be recalculated. So far, remove those rows from db_resurv_RS_short.

db_resurv_RS_short <- db_resurv_RS_short %>%
  filter(year_diff == FALSE | is.na(year_diff)) %>%
  filter(Lon_diff == "NO" |Lon_diff == "SMALL" | is.na(Lon_diff)) %>%
  filter(Lat_diff == "NO" |Lat_diff == "SMALL" | is.na(Lat_diff))

TO-DO: Recalculate RS indices for those above

Handle plots that have more than one obs per year

Add column PLOT to data to identify unique plots:

db_resurv_RS_short_PLOT <- db_resurv_RS_short %>%
  # Original names give problems, create new vars
  mutate(RS_site = `ReSurvey site`, RS_plot = `ReSurvey plot`) %>%
  # Convert to data.table for faster processing
  lazy_dt() %>%
  # Group by the 3 vars that uniquely identify each pl ot
  group_by(RS_CODE, RS_site, RS_plot) %>%
  # Create a new variable PLOT for each group
  mutate(PLOT = .GRP) %>%
  # Convert back to tibble
  as_tibble() %>%
  # Remove unneeded vars
  select(-RS_site, -RS_plot)

There should be only one observation of each plot per year.

Plots where there is at least a year with more than one observation, and where those observations have a different EUNIS assigned:

plots_to_remove <- db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  summarize(EUNISa_1_n = n_distinct(EUNISa_1, na.rm = TRUE)) %>%
  ungroup() %>% 
  filter(EUNISa_1_n > 1) %>%
  distinct(PLOT)
`summarise()` has grouped output by 'PLOT'. You can override using the `.groups` argument.

Remove plots_to_remove from the database:

db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
  anti_join(plots_to_remove, by = "PLOT")

Plots and years where there is more than one observation:

plots_to_merge <- db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  # Plots that have more than one observation per year
  filter(n() > 1) %>%
  ungroup() %>%
  distinct(PLOT)

Summarize plots_to_merge:

plots_to_merge_summ <- db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  # Plots that have more than one observation per year
  filter(n() > 1) %>%
  mutate(obs_num = row_number()) %>%
  pivot_wider(
    names_from = obs_num,
    values_from = c(date, PlotObservationID),
    names_prefix = "obs_"
  ) %>%
  arrange(PLOT) %>%
  summarize(
    across(c(Country, RS_CODE, `ReSurvey site`, `ReSurvey plot`,
             `Manipulate (y/n)`, `Type of manipulation`, Lon_updated,
             Lat_updated, `Location method`, `Location uncertainty (m)`,
             EUNISa_1, EUNISa_1_descr, EUNISa_2, EUNISa_2_descr, EUNISa_3,
             EUNISa_3_descr, EUNISa_4, EUNISa_4_descr, EUNIS1_conf,
             EUNIS1_conf_type, biogeo, unit, year_RS, Lon_RS, Lat_RS, NDVI_max,
             NDVI_median, NDVI_min, NDVI_mode, NDVI_p10, NDVI_p90, NDMI_max,
             NDMI_median, NDMI_min, NDMI_mode, NDMI_p10, NDMI_p90, NDWI_max,
             NDWI_median, NDWI_min, NDWI_mode, NDWI_p10, NDWI_p90, EVI_max,
             EVI_median, EVI_min, EVI_mode, EVI_p10, EVI_p90, SAVI_max,
             SAVI_median, SAVI_min, SAVI_mode, SAVI_p10, SAVI_p90,
             canopy_height, SOS_DOY, SOS_date, Peak_DOY, Peak_date, EOS_DOY,
             EOS_date, S2_data, Landsat_data, CH_data, S2_phen_data, year_diff,
             Lon_diff, Lat_diff), first),
    across(starts_with("date_obs_"), min),
    across(starts_with("PlotObservationID_obs_"), min)
    ) %>%
  ungroup()
`summarise()` has grouped output by 'PLOT'. You can override using the `.groups` argument.

Remove plots_to_merge from the database:

db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
  anti_join(plots_to_merge, by = "PLOT")

And add plots_to_merge_summ, where each plot and year only has one row:

db_resurv_RS_short_PLOT <- bind_rows(db_resurv_RS_short_PLOT,
                                     plots_to_merge_summ)

Check that there is only one row per plot and per year:

db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  # Plots that have more than one observation per year
  filter(n() > 1) 

So, to sum up what I have done:

  • Plots where there is at least a year with more than one observation, and where those observations have a different EUNIS assigned: Plots REMOVED from the data
  • Plots where there is more than one observation, but observations have the same EUNIS assigned: kept in the data. Merged so that there is only one row per year. Info about the different dates (when different) is kept in columns date_obs_1 - date_obs_40, and info about the different PlotObservationID is kept in the columns PlotObservationID_obs_1 - PlotObservationID_obs_40.

Save to clean data

Save clean file for analyses (to be updated continuously due to updates in ReSurvey database and updates on RS data).

write_tsv(db_resurv_RS_short_PLOT,
          here("data", "clean","db_resurv_RS_short_PLOT_20250505.csv"))

Distributions all bioregions

# Define a function to create histograms
plot_histogram <- function(data, x_var, x_label) {
  ggplot(data %>%
           filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
         aes(x = !!sym(x_var))) +
    geom_histogram(color = "black", fill = "white") +
    labs(x = x_label, y = "Frequency") +
    theme_bw()
}
# Define a function to create plots with violin + boxplot + points
distr_plot <- function(data, y_vars, y_labels) {
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    p <- ggplot(data = data %>%
                  filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
      geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
                 position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
      geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
      stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                     label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNIS level 1") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip()
    
    print(p)
  }
}

HERE: Metrics calculated only for July, not for all year!

NDVI, NDMI, NDWI, SAVI and EVI

Histograms to check that values are ok:

hist_NDVI <- plot_histogram(db_resurv_RS_short_PLOT, "NDVI_max", "NDVI max")
hist_NDMI <- plot_histogram(db_resurv_RS_short_PLOT, "NDMI_max", "NDMI max")
hist_NDWI <- plot_histogram(db_resurv_RS_short_PLOT, "NDWI_max", "NDWI max")
hist_SAVI <- plot_histogram(db_resurv_RS_short_PLOT, "SAVI_max", "SAVI max")
hist_EVI <- plot_histogram(db_resurv_RS_short_PLOT, "EVI_max", "EVI max")
hist_NDVI

hist_NDMI

hist_NDWI

hist_SAVI

hist_EVI # Some values wrong!

hist_EVI_1 <- plot_histogram(db_resurv_RS_short_PLOT %>% filter(EVI_max <= 1),
                             "EVI_max", "EVI max")
hist_EVI_1

nrow(db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
       filter(EVI_max > 1))
[1] 1130
db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q"))%>%
  filter(EVI_max > 1) %>%
  count(biogeo, unit)

So far, remove observations with EVI_max > 1 from stuff using EVI values.

Distribution plots:

plot_distr_NDVI <- distr_plot(db_resurv_RS_short_PLOT, "NDVI_max", "NDVI max")
plot_distr_NDMI <- distr_plot(db_resurv_RS_short_PLOT, "NDMI_max", "NDMI max")
plot_distr_NDWI <- distr_plot(db_resurv_RS_short_PLOT, "NDWI_max", "NDWI max")
plot_distr_SAVI <- distr_plot(db_resurv_RS_short_PLOT, "SAVI_max", "SAVI max")
plot_distr_EVI <- distr_plot(db_resurv_RS_short_PLOT %>% filter(EVI_max <= 1),
                             "EVI_max", "EVI max")
plot_distr_NDVI

plot_distr_NDMI

plot_distr_NDWI

plot_distr_SAVI

plot_distr_EVI

CH

plot_distr_CH <- distr_plot(db_resurv_RS_short_PLOT, "canopy_height",
                            "Canopy height (m)")
plot_distr_CH

Show habitats with CH categories

ggplot(db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
         mutate(CH_cat =
                  factor(
                    case_when(canopy_height == 0 ~ "0 m",
                              canopy_height > 0 & canopy_height <= 1 ~ "0-1 m",
                              canopy_height > 1 & canopy_height <=2 ~ "1-2 m",
                              canopy_height > 2 & canopy_height <=5 ~ "2-5 m",
                              canopy_height > 5 & canopy_height <=8 ~ "5-8 m",
                              canopy_height > 8 ~ "> 8 m",
                              is.na(canopy_height) ~ NA_character_),
                    levels = c(
                      "0 m", "0-1 m", "1-2 m", "2-5 m", "5-8 m", "> 8 m"))),
       aes(x = EUNISa_1_descr, fill = CH_cat)) +
  geom_bar() + theme_bw() + coord_flip() +
  scale_y_continuous(labels = label_number()) +
  scale_fill_viridis_d(direction = -1) +
  labs(x = "EUNIS level 1", fill = "Canopy height") +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  theme(legend.position = c(0.8, 0.75),
        legend.direction = "vertical")

Stats per habitat type

db_resurv_RS_short_PLOT %>%
  # Keep only forests, grasslands, shrublands and wetlands
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  group_by(EUNISa_1_descr) %>%
  summarise(across(canopy_height, list(
    mean = mean,
    median = median,
    sd = sd,
    min = min,
    max = max
    ), na.rm = TRUE))

Phenology

Calculate metrics

db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
  mutate(
    # Difference NDVI between Peak and SOS
    diff_Peak_SOS = NDVI_at_Peak - NDVI_at_SOS,
    # Difference NDVI between Peak and EOS
    diff_Peak_EOS = NDVI_at_Peak - NDVI_at_EOS)

Histograms phenology measures

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(SOS_DOY, Peak_DOY, EOS_DOY), names_to = "name",
                      values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(NDVI_at_SOS, NDVI_at_Peak, NDVI_at_EOS),
                      names_to = "name", values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(diff_Peak_SOS, diff_Peak_EOS),
                      names_to = "name", values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(Season_Length),
                      names_to = "name", values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

Distributions

plot_distr_SOS_DOY <- distr_plot(db_resurv_RS_short_PLOT, "SOS_DOY",
                                 "SOS DOY")
plot_distr_Peak_DOY <- distr_plot(db_resurv_RS_short_PLOT, "Peak_DOY",
                                  "Peak DOY")
plot_distr_EOS_DOY <- distr_plot(db_resurv_RS_short_PLOT, "EOS_DOY",
                                 "EOS DOY")
plot_distr_NDVI_at_SOS <- distr_plot(db_resurv_RS_short_PLOT, "NDVI_at_SOS",
                                     "NDVI at SOS")
plot_distr_NDVI_at_Peak <- distr_plot(db_resurv_RS_short_PLOT, "NDVI_at_Peak",
                                      "NDVI at Peak")
plot_distr_NDVI_at_EOS <- distr_plot(db_resurv_RS_short_PLOT, "NDVI_at_EOS",
                                     "NDVI at EOS")
plot_distr_diff_Peak_SOS <- distr_plot(db_resurv_RS_short_PLOT, "diff_Peak_SOS",
                                       "Difference Peak-SOS")
plot_distr_diff_Peak_EOS <- distr_plot(db_resurv_RS_short_PLOT, "diff_Peak_EOS",
                                       "Difference Peak-EOS")
plot_distr_Season_Length <- distr_plot(db_resurv_RS_short_PLOT, "Season_Length",
                                       "Season Length")
plot_distr_SOS_DOY

plot_distr_Peak_DOY

plot_distr_EOS_DOY

plot_distr_NDVI_at_SOS

plot_distr_NDVI_at_Peak

plot_distr_NDVI_at_EOS

plot_distr_diff_Peak_SOS

plot_distr_diff_Peak_EOS

plot_distr_Season_Length

Distributions per bioregion

# Define a function to create plots with violin + boxplot + points
distr_plot_biogeo <- function(data, y_var, y_label) {
  ggplot(data = data %>%
           filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
         aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
    geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
    geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
               position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
    geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
    stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
    stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                   label = length(x)),
                 geom = "text", aes(label = ..label..), vjust = 0.5) +
    labs(y = y_label, x = "EUNISa_1_descr") +
    scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
    guides(fill = FALSE, color = FALSE) +
    theme_bw() + coord_flip() + facet_wrap(~ biogeo)
}

Max. NDVI, NDMI, NDWI, SAVI and EVI

Distribution plots:

plot_distr_NDVI_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                            "NDVI_max", "NDVI max")
plot_distr_NDMI_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                            "NDMI_max", "NDMI max")
plot_distr_NDWI_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                            "NDWI_max", "NDWI max")
plot_distr_SAVI_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                            "SAVI_max", "SAVI max")
plot_distr_EVI_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)) %>%
                                             filter(EVI_max <= 1),
                                           "EVI_max", "EVI max")
plot_distr_NDVI_biogeo

plot_distr_NDMI_biogeo

plot_distr_NDWI_biogeo

plot_distr_SAVI_biogeo

plot_distr_EVI_biogeo

BOR missing because there is no EUNIS info for those that have RS info.

CH

plot_distr_CH_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT,
                                          "canopy_height", "Canopy height (m)")
plot_distr_CH_biogeo

In this plot, those with biogeo = NA are those that do not have S2 or Landsat data (and thus biogeo has not been assigned), but have CH data. We should later assign a biogeo based on location.

Phenology

plot_distr_SOS_DOY_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                               "SOS_DOY", "SOS DOY")
plot_distr_Peak_DOY_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                                "Peak_DOY", "Peak DOY")
plot_distr_EOS_DOY_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                               "EOS_DOY", "EOS DOY")
plot_distr_NDVI_at_SOS_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                               "NDVI_at_SOS", "NDVI at SOS")
plot_distr_NDVI_at_Peak_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                                "NDVI_at_Peak", "NDVI at Peak")
plot_distr_NDVI_at_EOS_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                               "NDVI_at_EOS", "NDVI at EOS")
plot_distr_diff_Peak_SOS_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                               "diff_Peak_SOS", "Difference Peak-SOS")
plot_distr_diff_Peak_EOS_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                               "diff_Peak_EOS", "Difference Peak-EOS")
plot_distr_Season_Length_biogeo <- distr_plot_biogeo(db_resurv_RS_short_PLOT %>%
                                              filter(!is.na(biogeo)),
                                               "Season_Length", "Season Length")

plot_distr_SOS_DOY_biogeo

plot_distr_Peak_DOY_biogeo

plot_distr_EOS_DOY_biogeo

plot_distr_NDVI_at_SOS_biogeo

plot_distr_NDVI_at_Peak_biogeo

plot_distr_NDVI_at_EOS_biogeo

plot_distr_diff_Peak_SOS_biogeo

plot_distr_diff_Peak_EOS_biogeo

plot_distr_Season_Length_biogeo

BOR missing because there is no EUNIS info for those that have RS info.

HERE: Verify SOS-Peak_EOS ODY

ERRORS! Double-check and send to Bea:

db_resurv_RS_short_PLOT %>% filter(SOS_DOY > Peak_DOY)
db_resurv_RS_short_PLOT %>% filter(Peak_DOY > EOS_DOY)
db_resurv_RS_short_PLOT %>% filter(SOS_DOY > EOS_DOY)
db_resurv_RS_short_PLOT %>% filter(NDVI_at_Peak < NDVI_at_SOS)
db_resurv_RS_short_PLOT %>% filter(NDVI_at_Peak < NDVI_at_EOS)

Comparison differential GPS vs. other points

# Define function
distr_plot_GPS <- function(data, y_var, y_label) {
  ggplot(data = data %>%
           filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
           mutate(GPS = 
                    ifelse(
                      is.na(`Location method`) |
                        `Location method` != "Location with differential GPS",
                      "Other",
                      "Differential GPS")),
         aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
    geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
    geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
               position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
    geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
    stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
    stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                   label = length(x)),
                 geom = "text", aes(label = ..label..), vjust = 0.5) +
    labs(y = y_label, x = "EUNIS level 1") +
    scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
    guides(fill = FALSE, color = FALSE) +
    theme_bw() + coord_flip() + facet_wrap(~ GPS)
}
plot_distr_NDVI_GPS <- distr_plot_GPS(db_resurv_RS_short_PLOT,
                                      "NDVI_max", "NDVI max")
plot_distr_NDMI_GPS <- distr_plot_GPS(db_resurv_RS_short_PLOT,
                                      "NDMI_max", "NDMI max")
plot_distr_NDWI_GPS <- distr_plot_GPS(db_resurv_RS_short_PLOT,
                                      "NDWI_max", "NDWI max")
plot_distr_SAVI_GPS <- distr_plot_GPS(db_resurv_RS_short_PLOT,
                                      "SAVI_max", "SAVI max")
plot_distr_EVI_GPS <- distr_plot_GPS(db_resurv_RS_short_PLOT %>%
                                       filter(EVI_max <= 1),
                                      "EVI_max", "EVI max")
plot_distr_NDVI_GPS

plot_distr_NDMI_GPS

plot_distr_NDWI_GPS

plot_distr_SAVI_GPS

plot_distr_EVI_GPS

Including PLOT (USE LATER?)

Summarize variables by plot:

db_resurv_RS_short_PLOT_summ <- db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  filter(S2_data == T | Landsat_data == T) %>%
  group_by(PLOT) %>%
  summarize(EUNIS1 = if_else(n_distinct(EUNISa_1) > 1, "Change",
                             unique(EUNISa_1)[1]),
            count = n(),
            across(starts_with("NDVI"), list(mean = mean, sd = sd),
                   .names = "{col}_{fn}"))

Maybe use later because now many plots have only one observation, probably because some Landsat data is missing?

First validation

For T, R, S, Q habitats.

Define a set of rules for a first validation of ALL ReSurvey data. We can call these “Expert-based” rules.

Number of observations in ReSurvey from the habitats of interest:

nrow(db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q")))
[1] 181061

Number of observations in ReSurvey from the habitats of interest and with all RS data:

nrow(db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
       filter(CH_data == T) %>%
       filter(S2_data == T | Landsat_data ==T) %>%
       filter(S2_phen_data == T))
[1] 12219
db_resurv_RS_short_PLOT_terrestrial <- db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q"))

Define rules

Create column for first validation based on different indicators, where “wrong” is noted when the validation rule is not met. Include EUNIS1 confusions.

db_resurv_RS_short_PLOT_terrestrial %>% count(EUNISa_1, EUNIS1_conf_type)

Define rules:

db_resurv_RS_short_PLOT_terrestrial <-
  db_resurv_RS_short_PLOT_terrestrial %>%
  mutate(
    valid_1_NDWI = case_when(
      # Points that are basically water
      NDWI_max > 0.3 ~ "wrong",
      TRUE ~ NA_character_),
    valid_1_CH = case_when(
      # T points with low CH
      EUNISa_1 == "T" & canopy_height < 8 ~ "wrong",
      # S points with low CH
      EUNISa_1 =="S" & canopy_height < 5 ~ "wrong",
      # R & Q points with high CH
      EUNISa_1 %in% c("R", "Q") & canopy_height > 2 ~ "wrong",
      TRUE ~ NA_character_),
    valid_1_NDVI = case_when(
      # T points with low NDVI_max
      EUNISa_1 == "T" & NDVI_max < 0.6 ~ "wrong",
      # S-R-Q points with low NDVI_max
      EUNISa_1 %in% c("R", "S", "Q") & NDVI_max < 0.2 ~ "wrong",
      TRUE ~ NA_character_),
    # Count how many validation rules are not met
    valid_1_count = rowSums(across(c(valid_1_NDWI, valid_1_CH, valid_1_NDVI), 
                             ~ . == "wrong"), na.rm = TRUE),
    # Points where at least 1 rule not met
    valid_1 = if_else(valid_1_count > 0, "At least 1 rule broken",
                      "No rules broken so far")
    )

Plots first validation

ggplot(db_resurv_RS_short_PLOT_terrestrial%>%
         mutate(rules_broken = case_when(
           valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
           valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
           valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
           valid_1_count == 2 &
             valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
           valid_1_count == 3 ~ "NDWI + NDVI + CH",
           TRUE ~ NA_character_
         )), 
       aes(x = valid_1_count, fill = rules_broken)) +
  geom_bar() + labs(x = "Number of broken rules")

db_resurv_RS_short_PLOT_terrestrial %>%
         mutate(rules_broken = case_when(
           valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
           valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
           valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
           valid_1_count == 2 &
             valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
           valid_1_count == 3 ~ "NDWI + NDVI + CH",
           TRUE ~ NA_character_
         )) %>%
  count(rules_broken, EUNIS1_conf_type)

Proportion of observations not validated (so far):

nrow(db_resurv_RS_short_PLOT_terrestrial %>% filter(valid_1_count > 0))/
  nrow(db_resurv_RS_short_PLOT_terrestrial)
[1] 0.1941721

But be aware that there are still many missing RS data, e.g. Landsat data for bioregion CON (many points).

ggplot(db_resurv_RS_short_PLOT_terrestrial %>%
         mutate(diff_GPS = if_else(
           `Location method` != "Location with differential GPS" |
             is.na(`Location method`), "no", "yes")), 
       aes(x = diff_GPS, fill = valid_1)) +
  geom_bar() + labs(x = "Differential GPS")

ggplot(db_resurv_RS_short_PLOT_terrestrial %>%
         mutate(GPS = case_when(
           `Location method` == "Location with differential GPS" ~ "yes",
           `Location method` == "Location with GPS" ~ "yes",
           is.na(`Location method`) ~ "no",
           TRUE ~ "no"
         )), 
       aes(x = GPS, fill = valid_1)) +
  geom_bar() + labs(x = "GPS")

Points with any rule broken and confusion between EUNIS:

nrow(db_resurv_RS_short_PLOT_terrestrial %>%
       filter(EUNIS1_conf == T & valid_1_count > 0))
[1] 9

Convert to shp to look at these in GIS:

# st_write(db_resurv_RS_short_PLOT_terrestrial %>%
#            filter(EUNIS1_conf == T & valid_1_count > 0) %>%
#            st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
#          "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_EUNIS_conf.shp")

Checked and yes

How many points with differential GPS that have at least 1 rule broken?

nrow(db_resurv_RS_short_PLOT_terrestrial %>%
  filter(`Location method` == "Location with differential GPS" &
           valid_1 == "At least 1 rule broken"))
[1] 1202

Convert to shp to look at these in GIS:

# st_write(db_resurv_RS_short_PLOT_terrestrial %>%
#            filter(`Location method` == "Location with differential GPS" &
#                     valid_1 == "At least 1 rule broken") %>%
#            st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
#          "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_diff_GPS.shp")

Distributions from diff GPS points without rules broken so far

Create tibble with differential GPS points without rules broken so far:

diff_GPS_valid <- db_resurv_RS_short_PLOT_terrestrial %>%
  filter(`Location method` == "Location with differential GPS" &
           valid_1 == "No rules broken so far") 

Max. and min. NDVI, NDMI, NDWI, SAVI and EVI

plot_distr_NDVI_max_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "NDVI_max", "NDVI max")
plot_distr_NDMI_max_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "NDMI_max", "NDMI max")
plot_distr_NDWI_max_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "NDWI_max", "NDWI max")
plot_distr_SAVI_max_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "SAVI_max", "SAVI max")
plot_distr_EVI_max_diff_GPS_valid <- distr_plot(diff_GPS_valid %>%
                                              filter(EVI_max <= 1),
                                            "EVI_max", "EVI max")
plot_distr_NDVI_max_diff_GPS_valid

plot_distr_NDMI_max_diff_GPS_valid

plot_distr_NDWI_max_diff_GPS_valid

plot_distr_SAVI_max_diff_GPS_valid

plot_distr_EVI_max_diff_GPS_valid

plot_distr_NDVI_min_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "NDVI_min", "NDVI min")
plot_distr_NDMI_min_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "NDMI_min", "NDMI min")
plot_distr_NDWI_min_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "NDWI_min", "NDWI min")
plot_distr_SAVI_min_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                             "SAVI_min", "SAVI min")
plot_distr_EVI_min_diff_GPS_valid <- distr_plot(diff_GPS_valid %>%
                                                  # Some values wrong!
                                                  # EVI should not be lower than -1
                                                  filter(EVI_min >= -1),
                                                "EVI_min", "EVI min")
plot_distr_NDVI_min_diff_GPS_valid

plot_distr_NDMI_min_diff_GPS_valid

plot_distr_NDWI_min_diff_GPS_valid

plot_distr_SAVI_min_diff_GPS_valid

plot_distr_EVI_min_diff_GPS_valid

CH

plot_distr_CH_diff_GPS_valid <- distr_plot(diff_GPS_valid, "canopy_height",
                                           "Canopy height (m)")
plot_distr_CH_diff_GPS_valid

Phenology

plot_distr_SOS_DOY_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                                "SOS_DOY", "SOS DOY")
plot_distr_Peak_DOY_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                                 "Peak_DOY", "Peak DOY")
plot_distr_EOS_DOY_diff_GPS_valid <- distr_plot(diff_GPS_valid,
                                                "EOS_DOY", "EOS DOY")
plot_distr_SOS_DOY_diff_GPS_valid

plot_distr_Peak_DOY_diff_GPS_valid

plot_distr_EOS_DOY_diff_GPS_valid

Not very conclusive…

Distributions from GPS (diff or not) points without rules broken so far

Create tibble with differential GPS points without rules broken so far:

all_GPS_valid <- db_resurv_RS_short_PLOT_terrestrial %>%
  filter((`Location method` == "Location with differential GPS" | 
            `Location method` == "Location with GPS" ) &
           valid_1 == "No rules broken so far") 

Max. and min. NDVI, NDMI, NDWI, SAVI and EVI

plot_distr_NDVI_max_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "NDVI_max", "NDVI max")
plot_distr_NDMI_max_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "NDMI_max", "NDMI max")
plot_distr_NDWI_max_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "NDWI_max", "NDWI max")
plot_distr_SAVI_max_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "SAVI_max", "SAVI max")
plot_distr_EVI_max_all_GPS_valid <- distr_plot(all_GPS_valid %>%
                                              filter(EVI_max <= 1),
                                            "EVI_max", "EVI max")
plot_distr_NDVI_max_all_GPS_valid

plot_distr_NDMI_max_all_GPS_valid

plot_distr_NDWI_max_all_GPS_valid

plot_distr_SAVI_max_all_GPS_valid

plot_distr_EVI_max_all_GPS_valid

plot_distr_NDVI_min_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "NDVI_min", "NDVI min")
plot_distr_NDMI_min_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "NDMI_min", "NDMI min")
plot_distr_NDWI_min_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "NDWI_min", "NDWI min")
plot_distr_SAVI_min_all_GPS_valid <- distr_plot(all_GPS_valid,
                                             "SAVI_min", "SAVI min")
plot_distr_EVI_min_all_GPS_valid <- distr_plot(all_GPS_valid %>%
                                                  # Some values wrong!
                                                  # EVI should not be lower than -1
                                                  filter(EVI_min >= -1),
                                                "EVI_min", "EVI min")
plot_distr_NDVI_min_all_GPS_valid

plot_distr_NDMI_min_all_GPS_valid

plot_distr_NDWI_min_all_GPS_valid

plot_distr_SAVI_min_all_GPS_valid

plot_distr_EVI_min_all_GPS_valid

CH

plot_distr_CH_all_GPS_valid <- distr_plot(all_GPS_valid, "canopy_height",
                                           "Canopy height (m)")
plot_distr_CH_all_GPS_valid

Phenology

plot_distr_SOS_DOY_all_GPS_valid <- distr_plot(all_GPS_valid,
                                                "SOS_DOY", "SOS DOY")
plot_distr_Peak_DOY_all_GPS_valid <- distr_plot(all_GPS_valid,
                                                 "Peak_DOY", "Peak DOY")
plot_distr_EOS_DOY_all_GPS_valid <- distr_plot(all_GPS_valid,
                                                "EOS_DOY", "EOS DOY")
plot_distr_SOS_DOY_all_GPS_valid

plot_distr_Peak_DOY_all_GPS_valid

plot_distr_EOS_DOY_all_GPS_valid

Combined plot

grid.arrange(
  plot_distr_NDVI_max_diff_GPS_valid, plot_distr_NDVI_max_all_GPS_valid,
  plot_distr_NDMI_min_diff_GPS_valid, plot_distr_NDMI_min_all_GPS_valid,
  ncol = 2)

Diff GPS valid points above p20 of NDVI_max and NDMI_min for each habitat

percentiles_diff_GPS <- diff_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(percentile_20_NDVI_max = quantile(NDVI_max, probs = 0.20, na.rm = T),
            percentile_20_NDMI_min = quantile(NDMI_min, probs = 0.20, na.rm = T))

diff_GPS_valid <- diff_GPS_valid %>%
  left_join(percentiles_diff_GPS, by = "EUNISa_1") %>%
  mutate(category_NDVI_max = case_when(
    NDVI_max < percentile_20_NDVI_max ~ "below_20th",
    NDVI_max >= percentile_20_NDVI_max ~ "above_20th"),
  category_NDMI_min = case_when(
    NDMI_min < percentile_20_NDMI_min ~ "below_20th",
    NDMI_min >= percentile_20_NDMI_min ~ "above_20th"))

ggplot(data = diff_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDVI_max)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDVI_max),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDVI max", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()


ggplot(data = diff_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDMI_min)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDMI_min),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDMI min", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()

GPS (diff or not) valid points above p20 of NDVI_max and NDMI_min for each habitat

percentiles_all_GPS <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(percentile_20_NDVI_max = quantile(NDVI_max, probs = 0.20, na.rm = T),
            percentile_20_NDMI_min = quantile(NDMI_min, probs = 0.20, na.rm = T))

all_GPS_valid <- all_GPS_valid %>%
  left_join(percentiles_all_GPS, by = "EUNISa_1") %>%
  mutate(category_NDVI_max = case_when(
    NDVI_max < percentile_20_NDVI_max ~ "below_20th",
    NDVI_max >= percentile_20_NDVI_max ~ "above_20th"),
  category_NDMI_min = case_when(
    NDMI_min < percentile_20_NDMI_min ~ "below_20th",
    NDMI_min >= percentile_20_NDMI_min ~ "above_20th"))

ggplot(data = all_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDVI_max)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDVI_max),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDVI max", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()


ggplot(data = all_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDMI_min)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDMI_min),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDMI min", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()

RF models

Using the conditional inference version of random forest (cforest in package party). Suggested if the data are highly correlated. Cforest is more stable in deriving variable importance values in the presence of highly correlated variables, thus providing better accuracy in calculating variable importance (ref below).

Hothorn, T., Hornik, K. and Zeileis, A. (2006) Unbiased Recursive Portioning: A Conditional Inference Framework. Journal of Computational and Graphical Statistics, 15, 651- 674. http://dx.doi.org/10.1198/106186006X133933

All GPS points above p20

Filter the data to get only GPS-points above p20 of NDVI_max and NDMI_min.

all_GPS_valid <- all_GPS_valid %>%
  select(-percentile_20_NDVI_max, -percentile_20_NDMI_min)
percentiles <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    percentile_20_NDVI_max = quantile(NDVI_max, 0.20, na.rm = T),
    percentile_20_NDMI_min = quantile(NDMI_min, 0.20, na.rm = T),
    percentile_80_NDVI_max = quantile(NDVI_max, 0.80, na.rm = T),
    percentile_80_NDMI_min = quantile(NDMI_min, 0.80, na.rm = T)
    )

# Join the percentiles back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(percentiles, by = "EUNISa_1")

# Filter rows above the 20th percentile for both variables for each category of EUNISa_1
filtered_data1 <- all_GPS_valid %>%
  filter(
    NDVI_max >= percentile_20_NDVI_max & NDMI_min >= percentile_20_NDMI_min
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices1 <- sample(1:nrow(filtered_data1), 0.7 * nrow(filtered_data1))
train_data1 <- filtered_data1[train_indices1, ]
test_data1 <- filtered_data1[-train_indices1, ]

Number of points per category for filtered data:

filtered_data1 %>% count(EUNISa_1)

Investigate package ggparty (e.g. autoplot function, and more).

TO-DO: Choose the hyperparameter mtry based on the square root of the number of predictor variables (Hastie et al., 2009)-

Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: Data mining, inference, and prediction. Springer Science & Business Media.

Maybe TO_DO: We variated ntree from 50 to 800 in steps of 50, leaving mtry constant at 2. Tis parameter variation showed that ntree=500 was optimal, while higher ntree led to no further model improvement (Supplementary Fig. S10). Subsequently, the hyperparameter mtry was varied from 2 to 8 with constant ntree=500. Here, mtry=3 led to the best results in almost all cases (Supplementary Fig. S11). Consequently, we chose ntree=500 and mtry=3 for our main analysis across all study sites.

rf_cforest1 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data1,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest1 <- predict(rf_cforest1, newdata = test_data1,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest1, test_data1$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  638  327    0    0
         R  260 1491    1    0
         S    0    0   38    4
         T    0    0   12   56

Overall Statistics
                                          
               Accuracy : 0.7863          
                 95% CI : (0.7708, 0.8013)
    No Information Rate : 0.6431          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.566           
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.7105   0.8201  0.74510  0.93333
Specificity            0.8305   0.7413  0.99856  0.99566
Pos Pred Value         0.6611   0.8510  0.90476  0.82353
Neg Pred Value         0.8604   0.6958  0.99533  0.99855
Prevalence             0.3177   0.6431  0.01804  0.02122
Detection Rate         0.2257   0.5274  0.01344  0.01981
Detection Prevalence   0.3414   0.6197  0.01486  0.02405
Balanced Accuracy      0.7705   0.7807  0.87183  0.96450

SurrogateTree –> does not work

varimp_rf_cforest1 <- party::varimp(rf_cforest1, conditional = F) 
# conditional = T adjusts for correlations between predictor variables
# Takes long!
# party::varimp(rf_cforest1, conditional = T) 
# conditional = T adjusts for correlations between predictor variables
# Takes long!

Variable Importance Plot

varimp_rf_cforest1_df <- data.frame(Variable = names(varimp_rf_cforest1),
                                    Importance = varimp_rf_cforest1)
ggplot(varimp_rf_cforest1_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

Tree Visualization

# Create a single conditional inference tree using ctree
single_tree1 <- ctree(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max + NDMI_min +
                       NDWI_max + NDWI_min + EVI_max + EVI_min + SAVI_max +
                       SAVI_min + canopy_height,
                     data = train_data1)

# Plot the single tree using
autoplot(single_tree1)

All GPS points within IQ range

Filter the data to get only GPS-points within IQ range of NDVI_max and NDMI_min.

IQ_ranges <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    Q1_NDVI_max = quantile(NDVI_max, 0.25, na.rm = T),
    Q1_NDMI_min = quantile(NDMI_min, 0.25, na.rm = T),
    Q3_NDVI_max = quantile(NDVI_max, 0.75, na.rm = T),
    Q3_NDMI_min = quantile(NDMI_min, 0.75, na.rm = T)
    )

# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(IQ_ranges, by = "EUNISa_1")

# Filter rows within the IQR range for both variables
filtered_data2 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= Q1_NDVI_max & NDVI_max <= Q3_NDVI_max) &
    (NDMI_min >= Q1_NDMI_min & NDMI_min <= Q3_NDMI_min)
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices2 <- sample(1:nrow(filtered_data2), 0.7 * nrow(filtered_data2))
train_data2 <- filtered_data2[train_indices2, ]
test_data2 <- filtered_data2[-train_indices2, ]

Number of points per category for filtered data:

filtered_data2 %>% count(EUNISa_1)
rf_cforest2 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data2,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest2 <- predict(rf_cforest2, newdata = test_data2,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest2, test_data2$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction   Q   R   S   T
         Q 310   5   0   0
         R  43 885   0   0
         S   0   0  14   2
         T   0   0   6  22

Overall Statistics
                                         
               Accuracy : 0.9565         
                 95% CI : (0.9439, 0.967)
    No Information Rate : 0.6915         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.8997         
                                         
 Mcnemar's Test P-Value : NA             

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.8782   0.9944  0.70000  0.91667
Specificity            0.9946   0.8917  0.99842  0.99525
Pos Pred Value         0.9841   0.9537  0.87500  0.78571
Neg Pred Value         0.9558   0.9861  0.99528  0.99841
Prevalence             0.2743   0.6915  0.01554  0.01865
Detection Rate         0.2409   0.6876  0.01088  0.01709
Detection Prevalence   0.2448   0.7211  0.01243  0.02176
Balanced Accuracy      0.9364   0.9430  0.84921  0.95596
varimp_rf_cforest2 <- party::varimp(rf_cforest2, conditional = F) 

Variable Importance Plot

varimp_rf_cforest2_df <- data.frame(Variable = names(varimp_rf_cforest2),
                                    Importance = varimp_rf_cforest2)
ggplot(varimp_rf_cforest2_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

All GPS points within mean +/- SD

Filter the data to get only GPS-points within mean +/- SD of NDVI_max and NDMI_min.

mean_sd <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    mean_NDVI_max = mean(all_GPS_valid$NDVI_max, na.rm = T),
    mean_NDMI_min = mean(all_GPS_valid$NDMI_min, na.rm = T),
    sd_NDVI_max = sd(all_GPS_valid$NDVI_max, na.rm = T),
    sd_NDMI_min = sd(all_GPS_valid$NDMI_min, na.rm = T)
    )

# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(mean_sd, by = "EUNISa_1")

# Filter rows within the specified range for both variables
filtered_data3 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= (mean_NDVI_max - sd_NDVI_max) & NDVI_max <= (mean_NDVI_max + sd_NDVI_max)) &
      (NDMI_min >= (mean_NDMI_min - sd_NDMI_min) & NDMI_min <= (mean_NDMI_min + sd_NDMI_min))
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices3 <- sample(1:nrow(filtered_data3), 0.7 * nrow(filtered_data3))
train_data3 <- filtered_data3[train_indices3, ]
test_data3 <- filtered_data3[-train_indices3, ]

Number of points per category for filtered data:

filtered_data3 %>% count(EUNISa_1)
rf_cforest3 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data3,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest3 <- predict(rf_cforest3, newdata = test_data3,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest3, test_data3$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  264  132    0    0
         R  511 1283    0    0
         S    0    0   36    2
         T    0    0   13   53

Overall Statistics
                                          
               Accuracy : 0.7132          
                 95% CI : (0.6942, 0.7316)
    No Information Rate : 0.6168          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.3741          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.3406   0.9067  0.73469  0.96364
Specificity            0.9131   0.4187  0.99911  0.99419
Pos Pred Value         0.6667   0.7152  0.94737  0.80303
Neg Pred Value         0.7308   0.7360  0.99424  0.99910
Prevalence             0.3378   0.6168  0.02136  0.02398
Detection Rate         0.1151   0.5593  0.01569  0.02310
Detection Prevalence   0.1726   0.7820  0.01656  0.02877
Balanced Accuracy      0.6269   0.6627  0.86690  0.97892
varimp_rf_cforest3 <- party::varimp(rf_cforest3, conditional = F) 

Variable Importance Plot

varimp_rf_cforest3_df <- data.frame(Variable = names(varimp_rf_cforest3),
                                    Importance = varimp_rf_cforest3)
ggplot(varimp_rf_cforest3_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

All GPS points above p20 and below p80

Filter the data to get only GPS-points above p20 and below p80 of NDVI_max and NDMI_min.

# Filter rows above the 20th percentile and below the 80th percentile for both variables
filtered_data4 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= percentile_20_NDVI_max & NDVI_max <= percentile_80_NDVI_max) &
    (NDMI_min >= percentile_20_NDMI_min & NDMI_min <= percentile_80_NDMI_min)
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices4 <- sample(1:nrow(filtered_data4), 0.7 * nrow(filtered_data4))
train_data4 <- filtered_data4[train_indices4, ]
test_data4 <- filtered_data4[-train_indices4, ]

Number of points per category for filtered data:

filtered_data4 %>% count(EUNISa_1)
rf_cforest4 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data4,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest4 <- predict(rf_cforest4, newdata = test_data4,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest4, test_data4$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  387   30    0    0
         R  149 1121    0    0
         S    0    0   17    1
         T    0    0   11   30

Overall Statistics
                                         
               Accuracy : 0.8906         
                 95% CI : (0.875, 0.9049)
    No Information Rate : 0.6592         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.7551         
                                         
 Mcnemar's Test P-Value : NA             

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.7220   0.9739 0.607143  0.96774
Specificity            0.9752   0.7496 0.999418  0.99359
Pos Pred Value         0.9281   0.8827 0.944444  0.73171
Neg Pred Value         0.8879   0.9370 0.993634  0.99941
Prevalence             0.3070   0.6592 0.016037  0.01775
Detection Rate         0.2216   0.6420 0.009737  0.01718
Detection Prevalence   0.2388   0.7274 0.010309  0.02348
Balanced Accuracy      0.8486   0.8618 0.803280  0.98066

HERE: Compare RF 1-4

Cordillera data

AlpineGrasslands_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrassland_Sentinel_Plot_Allyear_Allmetrics.csv")
Rows: 40 Columns: 69── Column specification ─────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (9): system:index, Codigo, H�bitat, Localidad, Precisi�n, Subregion, UTM, source, .geo
dbl (60): Altitude__, Altura_med, Altura_m�, Cobertur_1, Cobertur_2, Cobertura, DATE, EVI_max, EV...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
AlpineGrasslands_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
Rows: 40 Columns: 34── Column specification ─────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (8): system:index, Codigo, H�bitat, Localidad, Precisi�n, Subregion, UTM, .geo
dbl (26): Altitude__, Altura_med, Altura_m�, Cobertur_1, Cobertur_2, Cobertura, DATE, EOS_DOY, Gp...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
AlpineGrasslands_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_CanopyHeight_1m.csv")
Rows: 40 Columns: 27── Column specification ─────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (8): system:index, Codigo, Hábitat, Localidad, Precisión, Subregion, UTM, .geo
dbl (19): Altitude__, Altura_med, Altura_má, Cobertur_1, Cobertur_2, Cobertura, Date__year, Gps_e...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
VegetationTypes_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Sentinel_Plot_AllYear_Allmetrics.csv")
Rows: 32 Columns: 54── Column specification ─────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr   (8): system:index, EUNIS, ID, TYPE, Xo, Yo, source, .geo
dbl  (45): EVI_max, EVI_median, EVI_min, EVI_mode, EVI_p10, EVI_p90, EVI_stdDev, EVImean, NDMI_ma...
date  (1): DATE
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
VegetationTypes_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
Rows: 32 Columns: 19── Column specification ─────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr   (7): system:index, EUNIS, ID, TYPE, Xo, Yo, .geo
dbl  (11): EOS_DOY, NDVI_Amplitude, NDVI_at_EOS, NDVI_at_Peak, NDVI_at_SOS, Peak_DOY, SOS_DOY, Se...
date  (1): DATE
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
VegetationTypes_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_CanopyHeight_1m.csv")
Rows: 32 Columns: 12── Column specification ─────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): system:index, EUNIS, ID, TYPE, Xo, Yo, .geo
dbl  (4): X, Y, canopy_height, evaluated
date (1): DATE
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
AlpineGrasslands <- AlpineGrasslands_indices %>%
  select(-`system:index`, -.geo, -Localidad) %>%
  rename(Hábitat = "H�bitat") %>% 
  full_join(AlpineGrasslands_phen  %>%
              select(-`system:index`, -.geo, -Localidad) %>%
              rename(Hábitat = "H�bitat")) %>%
  full_join(AlpineGrasslands_CH  %>%
              select(-`system:index`, -.geo, -Localidad)) %>%
  select(-Date__year, - `Precisi�n`) %>%
  mutate(DATE = ymd(DATE)) %>%
  rename(ID = "Releve_num") %>%
  mutate(ID = as.character(ID)) %>%
  mutate(layer = "AlpineGrasslands")
Joining with `by = join_by(Altitude__, Altura_med, `Altura_m�`, Cobertur_1, Cobertur_2, Cobertura, Codigo, DATE, Gps_error, Hgtavg_hl, Hgtmax_hl, Hábitat, Latitude, Longitude, Num_specie, Orientaci, Pendiente, `Precisi�n`, Releve_num, Subregion, UTM, X, Y)`Joining with `by = join_by(Altitude__, Altura_med, Cobertur_1, Cobertur_2, Cobertura, Codigo, Gps_error, Hgtavg_hl, Hgtmax_hl, Hábitat, Latitude, Longitude, Num_specie, Orientaci, Pendiente, Releve_num, Subregion, UTM, X, Y)`
VegetationTypes <- VegetationTypes_indices %>%
  select(-`system:index`, -.geo) %>%
  full_join(VegetationTypes_phen  %>%
              select(-`system:index`, -.geo)) %>%
  full_join(VegetationTypes_CH  %>%
              select(-`system:index`, -.geo)) %>%
  rename(Hábitat = "TYPE") %>%
  mutate(layer = "VegetationTypes")
Joining with `by = join_by(DATE, EUNIS, ID, TYPE, X, Xo, Y, Yo, evaluated)`Joining with `by = join_by(DATE, EUNIS, ID, TYPE, X, Xo, Y, Yo, evaluated)`

Merge both datasets:

cordillera <- bind_rows(
  AlpineGrasslands %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer"),
  VegetationTypes %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer")
  ) %>%
  mutate(EUNISa_1 = case_when(
    Hábitat = str_detect(Hábitat, "Pastizal|Cervunal|grassland|meadow") ~ "R",
    Hábitat = str_detect(Hábitat, "forest") ~ "T",
    Hábitat = str_detect(Hábitat, "Scrub|scrub|Shrubland|shrubland|shrub|Heathland") ~ "S",
    Hábitat = str_detect(Hábitat, "Suelo|Scree|scree|cliff") ~ "U",
    Hábitat = is.na(Hábitat) ~ "R",
    TRUE ~ NA_character_),
    EUNISa_1_descr = case_when(
      EUNISa_1 == "R" ~ "Grasslands",
      EUNISa_1 == "T" ~ "Forests and other wooded land",
      EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISa_1 == "U" ~ "Inland habitats with no or little soil")
    )

Max. and min. NDVI, NDMI

plot_distr_NDVI_max_cordi <- distr_plot(cordillera,
                                             "NDVI_max", "NDVI max")
plot_distr_NDVI_min_cordi <- distr_plot(cordillera,
                                             "NDVI_min", "NDVI min")
plot_distr_NDMI_max_cordi <- distr_plot(cordillera,
                                             "NDMI_max", "NDMI max")
plot_distr_NDMI_min_cordi <- distr_plot(cordillera,
                                             "NDMI_min", "NDMI min")
plot_distr_NDVI_max_cordi

plot_distr_NDVI_min_cordi

plot_distr_NDMI_max_cordi

plot_distr_NDMI_min_cordi

OLD from here

Plots NDVI and NDMI

ggplot(data = cordi %>% filter(!is.na(NDVI_max)) %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNIS_1 %in% c("T", "R", "S", "Q")),
       aes(x = EUNIS_1, y = NDVI_max, fill = EUNIS_1)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
  geom_point(aes(y = NDVI_max, color = EUNIS_1),
             position = position_jitter(width = 0.15), size = 3,
             alpha = 0.5) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y=mean, geom="point", shape = 20, size = 3) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                 label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  guides(fill = FALSE, color = FALSE) + theme_bw() + coord_flip()
Error: objeto 'cordi' no encontrado

Maps

Points differential GPS

Maps

Number of differential GPS points by Country:

Points ReSurvey

Maps

Number of ReSurvey points by Country:

Join

Session info

LS0tDQp0aXRsZTogIlNjcmlwdCB0byB2YWxpZGF0ZSBwb2ludHMgaW4gUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgZGF0YSINCnN1YnRpdGxlOiAiQWRkaW5nIEFUTF9CRU5FTFVYIGFuZCBDT05fTk9SRElDIFMyIGRhdGEsIHNvbWUgTGFuZHNhdCBkYXRhLCBhZGRpbmcgY2Fub3B5IGhlaWdodCBkYXRhIg0KYXV0aG9yOiAiQWxpY2lhIFZhbGTDqXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpDQpgYGANCg0KVGhpcyBSIHNjcmlwdCBpcyB1c2VkIHRvIHZhbGlkYXRlIHRoZSBwb2ludHMgaW4gdGhlIFJlU3VydmV5IGRhdGFiYXNlIHVzaW5nIFJTIGluZGljYXRvcnMgKE5EVkksIE5ETUksIGNhbm9weSBoZWlnaHQpLg0KDQojIExvYWQgbGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShybmF0dXJhbGVhcnRoKQ0KbGlicmFyeShkdHBseXIpDQpsaWJyYXJ5KGxtZTQpDQpsaWJyYXJ5KGxtZXJUZXN0KQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KGdnZWZmZWN0cykNCmxpYnJhcnkocGFydHkpDQpsaWJyYXJ5KHBhcnR5a2l0KQ0KbGlicmFyeShzdHJ1Y2NoYW5nZSkNCmxpYnJhcnkoZ2dwYXJ0eSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KG1vcmVwYXJ0eSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KYGBgDQoNCiMgRGVmaW5lIHByaW50YWxsIGZ1bmN0aW9uDQoNCmBgYHtyfQ0KcHJpbnRhbGwgPC0gZnVuY3Rpb24odGliYmxlKSB7DQogIHByaW50KHRpYmJsZSwgd2lkdGggPSBJbmYpDQogIH0NCmBgYA0KDQojIExvYWQgZ2VvbV9mbGF0X3Zpb2xpbiBwbG90DQoNCmBgYHtyfQ0Kc291cmNlKCJodHRwczovL2dpc3QuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Jlbm1hcndpY2svMmExYmIwMTMzZmY1NjhjYmUyOGQvcmF3L2ZiNTNiZDk3MTIxZjdmOWNlOTQ3ODM3ZWYxYTRjNjVhNzNiZmZiM2YvZ2VvbV9mbGF0X3Zpb2xpbi5SIikNCmBgYA0KDQojIFJlYWQgUmVTdXJ2ZXkgZGF0YSB3aXRoIFJTIGluZGljYXRvcnMNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlM8LXJlYWRfdHN2KA0KICBoZXJlKCJkYXRhIiwgImNsZWFuIiwgImRiX3Jlc3Vydl9SU18yMDI1MDUwNS5jc3YiKSwNCiAgY29sX3R5cGVzID0gY29scygNCiAgICAjIER5bmFtaWNhbGx5IHNwZWNpZnkgRVVOSVMgY29sdW1ucyBhcyBjaGFyYWN0ZXINCiAgICAuZGVmYXVsdCA9IGNvbF9ndWVzcygpLCAgIyBEZWZhdWx0IGd1ZXNzaW5nIGZvciBvdGhlciBjb2x1bW5zDQogICAgRVVOSVNhID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYiA9IGNvbF9jaGFyYWN0ZXIoKSwNCiAgICBFVU5JU2MgPSBjb2xfY2hhcmFjdGVyKCksDQogICAgRVVOSVNkID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY180ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTX2Fzc2lnbmF0aW9uID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY180X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpDQogICAgKQ0KICApDQpgYGANCg0KTm8gcGFyc2luZyBpc3N1ZXMhDQoNCiMgU29tZSBkYXRhIG1hbmFnZW5lbXQNCg0KIyMgU2V2ZXJhbCBFVU5JUyBsZXZlbCAxIGFzc2lnbmVkDQoNCk51bWJlciBvZiByb3dzIHdoZXJlIHRoZXJlIGlzIG1vcmUgdGhhbiBvbmUgRVVOSVMgMSBhc3NpZ25lZCwgYW5kIHRoZXkgYXJlIGRpZmZlcmVudCBhbW9uZyB0aGVtLiBTZWUgd2hhdCB0byBkbyB3aXRoIHRoZXNlIGxhdGVyISBTbyBmYXIgSSB0YWtlIEVVTklTYV8xLg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTICU+JSANCiAgICAgICAjIFJvd3Mgd2l0aCBtb3JlIHRoYW4gb25lIEVVTklTIDEgYXNzaWduZWQNCiAgICAgICBmaWx0ZXIoIWlzLm5hKEVVTklTYl8xKSkgJT4lIA0KICAgICAgIGZpbHRlcihFVU5JU2FfMSE9RVVOSVNiXzEgfCBFVU5JU2JfMSAhPSBFVU5JU2NfMSB8IEVVTklTYV8xICE9IEVVTklTY18xKSkNCmBgYA0KDQpTZWUgImNvbmZ1c2lvbnMiOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SUyAlPiUgDQogICMgUm93cyB3aXRoIG1vcmUgdGhhbiBvbmUgRVVOSVMgMSBhc3NpZ25lZA0KICBmaWx0ZXIoIWlzLm5hKEVVTklTYl8xKSkgJT4lIA0KICBmaWx0ZXIoRVVOSVNhXzEhPUVVTklTYl8xIHwgRVVOSVNiXzEgIT0gRVVOSVNjXzEgfCBFVU5JU2FfMSAhPSBFVU5JU2NfMSkgJT4lDQogIGRpc3RpbmN0KEVVTklTYV8xLCBFVU5JU2JfMSwgRVVOSVNjXzEsIEVVTklTZF8xKQ0KYGBgDQoNCkRlZmluZSAiY29uZnVzaW9uIiBjb2x1bW5zOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SUyA8LSBkYl9yZXN1cnZfUlMgJT4lDQogIG11dGF0ZShFVU5JUzFfY29uZl90eXBlID0gY2FzZV93aGVuKA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJTIiAmIEVVTklTYl8xID09ICJUIiB+ICJTL1QiLA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJSIiAmIEVVTklTY18xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJSIiAmIEVVTklTY18xID09ICJTIiAmIEVVTklTZF8xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJQIiAmIEVVTklTYl8xID09ICJRIiB+ICJQL1EiLA0KICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICBFVU5JUzFfY29uZiA9ICFpcy5uYShFVU5JUzFfY29uZl90eXBlKSkNCmBgYA0KDQoNCiMjIFRpYmJsZSB3aXRoIHNlbGVjdGVkIGNvbHVtbnMNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgPC0gZGJfcmVzdXJ2X1JTICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIENvdW50cnksIFJTX0NPREUsIGBSZVN1cnZleSBzaXRlYCwgYFJlU3VydmV5IHBsb3RgLA0KICAgICAgICAgYE1hbmlwdWxhdGUgKHkvbilgLCBgVHlwZSBvZiBtYW5pcHVsYXRpb25gLCBMb25fdXBkYXRlZCwgTGF0X3VwZGF0ZWQsDQogICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCwgYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAsIEVVTklTYV8xLA0KICAgICAgICAgRVVOSVNhXzFfZGVzY3IsIEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgRVVOSVNhXzMsIEVVTklTYV8zX2Rlc2NyLA0KICAgICAgICAgRVVOSVNhXzQsIEVVTklTYV80X2Rlc2NyLCBFVU5JUzFfY29uZiwgRVVOSVMxX2NvbmZfdHlwZSwNCiAgICAgICAgIGRhdGUsIHllYXIsIGJpb2dlbywgdW5pdCwgeWVhcl9SUywgTG9uX1JTLCBMYXRfUlMsIA0KICAgICAgICAgc3RhcnRzX3dpdGgoIk5EVkkiKSwgc3RhcnRzX3dpdGgoIk5ETUkiKSwgc3RhcnRzX3dpdGgoIk5EV0kiKSwNCiAgICAgICAgIHN0YXJ0c193aXRoKCJFVkkiKSwgc3RhcnRzX3dpdGgoIlNBVkkiKSwgY2Fub3B5X2hlaWdodCwNCiAgICAgICAgIFNPU19ET1ksIFNPU19kYXRlLCBORFZJX2F0X1NPUywgUGVha19ET1ksIFBlYWtfZGF0ZSwgTkRWSV9hdF9QZWFrLA0KICAgICAgICAgRU9TX0RPWSwgRU9TX2RhdGUsIE5EVklfYXRfRU9TLCBTZWFzb25fTGVuZ3RoLA0KICAgICAgICAgUzJfZGF0YSwgTGFuZHNhdF9kYXRhLCBDSF9kYXRhLCBTMl9waGVuX2RhdGEpDQpgYGANCg0KIyMgVE8tRE86IE1pc3NpbmcgZGF0YSBjaGVja3MNCg0KRG8gd2hlbiBhbGwgUlMgZGF0YSBpcyByZWFkeSENCg0KIyMgRmxhZyB3aGVuIHllYXIgaXMgZGlmZmVyZW50IGJldHdlZW4gUlMgZGF0YSBhbmQgUmVTdXJ2ZXkgZGINCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JQ0KICBtdXRhdGUoeWVhcl9kaWZmID0geWVhciAhPSB5ZWFyX1JTKQ0KYGBgDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JSBjb3VudCh5ZWFyX2RpZmYpDQpgYGANCg0KMiB3aXRoIGRpZmZlcmVudCB5ZWFyLiBSUyBpbmRpY2VzIHdvdWxkIG5lZWQgdG8gYmUgY2FsY3VsYXRlZCBhZ2FpbiBmb3IgdGhvc2UuDQoNCiMjIEZsYWcgd2hlbiBjb29yZGluYXRlcyBhcmUgZGlmZmVyZW50IGJldHdlZW4gUlMgZGF0YSBhbmQgUmVTdXJ2ZXkgZGINCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JQ0KICBtdXRhdGUoTG9uX2RpZmYgPSBjYXNlX3doZW4oTG9uX3VwZGF0ZWQgPT0gTG9uX1JTIH4gIk5PIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU29tZXRpbWVzIHRoZXkgYXJlIG9ubHkgc2xpZ2hseSBkaWZmZXJlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFicyhMb25fdXBkYXRlZCAtIExvbl9SUykgPCAwLjAxIH4gIlNNQUxMIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKExvbl91cGRhdGVkKSB8IGlzLm5hKExvbl9SUykgfiBOQSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAiTEFSR0UiKSwNCiAgICAgICAgIExhdF9kaWZmID0gY2FzZV93aGVuKExhdF91cGRhdGVkID09IExhdF9SUyB+ICJOTyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNvbWV0aW1lcyB0aGV5IGFyZSBvbmx5IHNsaWdobHkgZGlmZmVyZW50DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhYnMoTGF0X3VwZGF0ZWQgLSBMYXRfUlMpIDwgMC4wMSB+ICJTTUFMTCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShMYXRfdXBkYXRlZCkgfCBpcy5uYShMYXRfUlMpIH4gTkEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIkxBUkdFIikpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgJT4lIGNvdW50KExvbl9kaWZmKQ0KZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JSBjb3VudChMYXRfZGlmZikNCmBgYA0KDQpWZXJ5IGZldyB3aXRoIGxhcmdlIGRpZmZlcmVuY2VzICgyIGZvciBsb25naXR1ZGUsIDYgZm9yIGxhdGl0dWRlKS4gUlMgaW5kaWNlcyB3b3VsZCBuZWVkIHRvIGJlIGNhbGN1bGF0ZWQgYWdhaW4gZm9yIHRob3NlLg0KDQpJZiB5ZWFyX2RpZmYgaXMgVFJVRSBvciBMb25fZGlmZiBpcyBMQVJHRSBvciBMYXRfZGlmZiBpcyBMQVJHRSAtLT4gUlMgaW5kaWNlcyBuZWVkIHRvIGJlIHJlY2FsY3VsYXRlZC4gU28gZmFyLCByZW1vdmUgdGhvc2Ugcm93cyBmcm9tIGRiX3Jlc3Vydl9SU19zaG9ydC4NCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JQ0KICBmaWx0ZXIoeWVhcl9kaWZmID09IEZBTFNFIHwgaXMubmEoeWVhcl9kaWZmKSkgJT4lDQogIGZpbHRlcihMb25fZGlmZiA9PSAiTk8iIHxMb25fZGlmZiA9PSAiU01BTEwiIHwgaXMubmEoTG9uX2RpZmYpKSAlPiUNCiAgZmlsdGVyKExhdF9kaWZmID09ICJOTyIgfExhdF9kaWZmID09ICJTTUFMTCIgfCBpcy5uYShMYXRfZGlmZikpDQpgYGANCg0KIyMgVE8tRE86IFJlY2FsY3VsYXRlIFJTIGluZGljZXMgZm9yIHRob3NlIGFib3ZlDQoNCiMjIEhhbmRsZSBwbG90cyB0aGF0IGhhdmUgbW9yZSB0aGFuIG9uZSBvYnMgcGVyIHllYXINCg0KQWRkIGNvbHVtbiBQTE9UIHRvIGRhdGEgdG8gaWRlbnRpZnkgdW5pcXVlIHBsb3RzOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UIDwtIGRiX3Jlc3Vydl9SU19zaG9ydCAlPiUNCiAgIyBPcmlnaW5hbCBuYW1lcyBnaXZlIHByb2JsZW1zLCBjcmVhdGUgbmV3IHZhcnMNCiAgbXV0YXRlKFJTX3NpdGUgPSBgUmVTdXJ2ZXkgc2l0ZWAsIFJTX3Bsb3QgPSBgUmVTdXJ2ZXkgcGxvdGApICU+JQ0KICAjIENvbnZlcnQgdG8gZGF0YS50YWJsZSBmb3IgZmFzdGVyIHByb2Nlc3NpbmcNCiAgbGF6eV9kdCgpICU+JQ0KICAjIEdyb3VwIGJ5IHRoZSAzIHZhcnMgdGhhdCB1bmlxdWVseSBpZGVudGlmeSBlYWNoIHBsIG90DQogIGdyb3VwX2J5KFJTX0NPREUsIFJTX3NpdGUsIFJTX3Bsb3QpICU+JQ0KICAjIENyZWF0ZSBhIG5ldyB2YXJpYWJsZSBQTE9UIGZvciBlYWNoIGdyb3VwDQogIG11dGF0ZShQTE9UID0gLkdSUCkgJT4lDQogICMgQ29udmVydCBiYWNrIHRvIHRpYmJsZQ0KICBhc190aWJibGUoKSAlPiUNCiAgIyBSZW1vdmUgdW5uZWVkZWQgdmFycw0KICBzZWxlY3QoLVJTX3NpdGUsIC1SU19wbG90KQ0KYGBgDQoNClRoZXJlIHNob3VsZCBiZSBvbmx5IG9uZSBvYnNlcnZhdGlvbiBvZiBlYWNoIHBsb3QgcGVyIHllYXIuDQoNClBsb3RzIHdoZXJlIHRoZXJlIGlzIGF0IGxlYXN0IGEgeWVhciB3aXRoIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb24sIGFuZCB3aGVyZSB0aG9zZSBvYnNlcnZhdGlvbnMgaGF2ZSBhIGRpZmZlcmVudCBFVU5JUyBhc3NpZ25lZDoNCg0KYGBge3J9DQpwbG90c190b19yZW1vdmUgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGdyb3VwX2J5KFBMT1QsIHllYXIpICU+JQ0KICBzdW1tYXJpemUoRVVOSVNhXzFfbiA9IG5fZGlzdGluY3QoRVVOSVNhXzEsIG5hLnJtID0gVFJVRSkpICU+JQ0KICB1bmdyb3VwKCkgJT4lIA0KICBmaWx0ZXIoRVVOSVNhXzFfbiA+IDEpICU+JQ0KICBkaXN0aW5jdChQTE9UKQ0KYGBgDQoNClJlbW92ZSBwbG90c190b19yZW1vdmUgZnJvbSB0aGUgZGF0YWJhc2U6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGFudGlfam9pbihwbG90c190b19yZW1vdmUsIGJ5ID0gIlBMT1QiKQ0KYGBgDQoNClBsb3RzIGFuZCB5ZWFycyB3aGVyZSB0aGVyZSBpcyBtb3JlIHRoYW4gb25lIG9ic2VydmF0aW9uOg0KDQpgYGB7cn0NCnBsb3RzX3RvX21lcmdlIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBncm91cF9ieShQTE9ULCB5ZWFyKSAlPiUNCiAgIyBQbG90cyB0aGF0IGhhdmUgbW9yZSB0aGFuIG9uZSBvYnNlcnZhdGlvbiBwZXIgeWVhcg0KICBmaWx0ZXIobigpID4gMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgZGlzdGluY3QoUExPVCkNCmBgYA0KDQpTdW1tYXJpemUgcGxvdHNfdG9fbWVyZ2U6DQoNCmBgYHtyfQ0KcGxvdHNfdG9fbWVyZ2Vfc3VtbSA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZ3JvdXBfYnkoUExPVCwgeWVhcikgJT4lDQogICMgUGxvdHMgdGhhdCBoYXZlIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb24gcGVyIHllYXINCiAgZmlsdGVyKG4oKSA+IDEpICU+JQ0KICBtdXRhdGUob2JzX251bSA9IHJvd19udW1iZXIoKSkgJT4lDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBvYnNfbnVtLA0KICAgIHZhbHVlc19mcm9tID0gYyhkYXRlLCBQbG90T2JzZXJ2YXRpb25JRCksDQogICAgbmFtZXNfcHJlZml4ID0gIm9ic18iDQogICkgJT4lDQogIGFycmFuZ2UoUExPVCkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBhY3Jvc3MoYyhDb3VudHJ5LCBSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCwNCiAgICAgICAgICAgICBgTWFuaXB1bGF0ZSAoeS9uKWAsIGBUeXBlIG9mIG1hbmlwdWxhdGlvbmAsIExvbl91cGRhdGVkLA0KICAgICAgICAgICAgIExhdF91cGRhdGVkLCBgTG9jYXRpb24gbWV0aG9kYCwgYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAsDQogICAgICAgICAgICAgRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLCBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIEVVTklTYV8zLA0KICAgICAgICAgICAgIEVVTklTYV8zX2Rlc2NyLCBFVU5JU2FfNCwgRVVOSVNhXzRfZGVzY3IsIEVVTklTMV9jb25mLA0KICAgICAgICAgICAgIEVVTklTMV9jb25mX3R5cGUsIGJpb2dlbywgdW5pdCwgeWVhcl9SUywgTG9uX1JTLCBMYXRfUlMsIE5EVklfbWF4LA0KICAgICAgICAgICAgIE5EVklfbWVkaWFuLCBORFZJX21pbiwgTkRWSV9tb2RlLCBORFZJX3AxMCwgTkRWSV9wOTAsIE5ETUlfbWF4LA0KICAgICAgICAgICAgIE5ETUlfbWVkaWFuLCBORE1JX21pbiwgTkRNSV9tb2RlLCBORE1JX3AxMCwgTkRNSV9wOTAsIE5EV0lfbWF4LA0KICAgICAgICAgICAgIE5EV0lfbWVkaWFuLCBORFdJX21pbiwgTkRXSV9tb2RlLCBORFdJX3AxMCwgTkRXSV9wOTAsIEVWSV9tYXgsDQogICAgICAgICAgICAgRVZJX21lZGlhbiwgRVZJX21pbiwgRVZJX21vZGUsIEVWSV9wMTAsIEVWSV9wOTAsIFNBVklfbWF4LA0KICAgICAgICAgICAgIFNBVklfbWVkaWFuLCBTQVZJX21pbiwgU0FWSV9tb2RlLCBTQVZJX3AxMCwgU0FWSV9wOTAsDQogICAgICAgICAgICAgY2Fub3B5X2hlaWdodCwgU09TX0RPWSwgU09TX2RhdGUsIFBlYWtfRE9ZLCBQZWFrX2RhdGUsIEVPU19ET1ksDQogICAgICAgICAgICAgRU9TX2RhdGUsIFMyX2RhdGEsIExhbmRzYXRfZGF0YSwgQ0hfZGF0YSwgUzJfcGhlbl9kYXRhLCB5ZWFyX2RpZmYsDQogICAgICAgICAgICAgTG9uX2RpZmYsIExhdF9kaWZmKSwgZmlyc3QpLA0KICAgIGFjcm9zcyhzdGFydHNfd2l0aCgiZGF0ZV9vYnNfIiksIG1pbiksDQogICAgYWNyb3NzKHN0YXJ0c193aXRoKCJQbG90T2JzZXJ2YXRpb25JRF9vYnNfIiksIG1pbikNCiAgICApICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KDQpSZW1vdmUgcGxvdHNfdG9fbWVyZ2UgZnJvbSB0aGUgZGF0YWJhc2U6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGFudGlfam9pbihwbG90c190b19tZXJnZSwgYnkgPSAiUExPVCIpDQpgYGANCg0KQW5kIGFkZCBwbG90c190b19tZXJnZV9zdW1tLCB3aGVyZSBlYWNoIHBsb3QgYW5kIHllYXIgb25seSBoYXMgb25lIHJvdzoNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCA8LSBiaW5kX3Jvd3MoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdHNfdG9fbWVyZ2Vfc3VtbSkNCmBgYA0KDQpDaGVjayB0aGF0IHRoZXJlIGlzIG9ubHkgb25lIHJvdyBwZXIgcGxvdCBhbmQgcGVyIHllYXI6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGdyb3VwX2J5KFBMT1QsIHllYXIpICU+JQ0KICAjIFBsb3RzIHRoYXQgaGF2ZSBtb3JlIHRoYW4gb25lIG9ic2VydmF0aW9uIHBlciB5ZWFyDQogIGZpbHRlcihuKCkgPiAxKSANCmBgYA0KDQpTbywgdG8gc3VtIHVwIHdoYXQgSSBoYXZlIGRvbmU6DQoNCi0gUGxvdHMgd2hlcmUgdGhlcmUgaXMgYXQgbGVhc3QgYSB5ZWFyIHdpdGggbW9yZSB0aGFuIG9uZSBvYnNlcnZhdGlvbiwgYW5kIHdoZXJlIHRob3NlIG9ic2VydmF0aW9ucyBoYXZlIGEgZGlmZmVyZW50IEVVTklTIGFzc2lnbmVkOiBQbG90cyBSRU1PVkVEIGZyb20gdGhlIGRhdGENCi0gUGxvdHMgd2hlcmUgdGhlcmUgaXMgbW9yZSB0aGFuIG9uZSBvYnNlcnZhdGlvbiwgYnV0IG9ic2VydmF0aW9ucyBoYXZlIHRoZSBzYW1lIEVVTklTIGFzc2lnbmVkOiBrZXB0IGluIHRoZSBkYXRhLiBNZXJnZWQgc28gdGhhdCB0aGVyZSBpcyBvbmx5IG9uZSByb3cgcGVyIHllYXIuIEluZm8gYWJvdXQgdGhlIGRpZmZlcmVudCBkYXRlcyAod2hlbiBkaWZmZXJlbnQpIGlzIGtlcHQgaW4gY29sdW1ucyBkYXRlX29ic18xIC0gZGF0ZV9vYnNfNDAsIGFuZCBpbmZvIGFib3V0IHRoZSBkaWZmZXJlbnQgUGxvdE9ic2VydmF0aW9uSUQgaXMga2VwdCBpbiB0aGUgY29sdW1ucyBQbG90T2JzZXJ2YXRpb25JRF9vYnNfMSAtIFBsb3RPYnNlcnZhdGlvbklEX29ic180MC4NCg0KIyMjIFNhdmUgdG8gY2xlYW4gZGF0YQ0KDQpTYXZlIGNsZWFuIGZpbGUgZm9yIGFuYWx5c2VzICh0byBiZSB1cGRhdGVkIGNvbnRpbnVvdXNseSBkdWUgdG8gdXBkYXRlcyBpbiBSZVN1cnZleSBkYXRhYmFzZSBhbmQgdXBkYXRlcyBvbiBSUyBkYXRhKS4NCg0KYGBge3J9DQp3cml0ZV90c3YoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgaGVyZSgiZGF0YSIsICJjbGVhbiIsImRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UXzIwMjUwNTA1LmNzdiIpKQ0KYGBgDQoNCiMgRGlzdHJpYnV0aW9ucyBhbGwgYmlvcmVnaW9ucw0KDQpgYGB7cn0NCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY3JlYXRlIGhpc3RvZ3JhbXMNCnBsb3RfaGlzdG9ncmFtIDwtIGZ1bmN0aW9uKGRhdGEsIHhfdmFyLCB4X2xhYmVsKSB7DQogIGdncGxvdChkYXRhICU+JQ0KICAgICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgICAgYWVzKHggPSAhIXN5bSh4X3ZhcikpKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIndoaXRlIikgKw0KICAgIGxhYnMoeCA9IHhfbGFiZWwsIHkgPSAiRnJlcXVlbmN5IikgKw0KICAgIHRoZW1lX2J3KCkNCn0NCmBgYA0KDQpgYGB7cn0NCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY3JlYXRlIHBsb3RzIHdpdGggdmlvbGluICsgYm94cGxvdCArIHBvaW50cw0KZGlzdHJfcGxvdCA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhcnMsIHlfbGFiZWxzKSB7DQogIGZvciAoaSBpbiBzZXFfYWxvbmcoeV92YXJzKSkgew0KICAgIHlfdmFyIDwtIHlfdmFyc1tbaV1dDQogICAgeV9sYWJlbCA8LSB5X2xhYmVsc1tbaV1dDQogICAgDQogICAgcCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEgJT4lDQogICAgICAgICAgICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIHkgPSAhIXN5bSh5X3ZhciksIGZpbGwgPSBFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44KSArDQogICAgICBnZW9tX3BvaW50KGFlcyh5ID0gISFzeW0oeV92YXIpLCBjb2xvciA9IEVVTklTYV8xX2Rlc2NyKSwNCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICAgICAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogICAgICBsYWJzKHkgPSB5X2xhYmVsLCB4ID0gIkVVTklTIGxldmVsIDEiKSArDQogICAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogICAgICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogICAgICB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpDQogICAgDQogICAgcHJpbnQocCkNCiAgfQ0KfQ0KYGBgDQoNCiMjIEhFUkU6IE1ldHJpY3MgY2FsY3VsYXRlZCBvbmx5IGZvciBKdWx5LCBub3QgZm9yIGFsbCB5ZWFyIQ0KDQojIyBORFZJLCBORE1JLCBORFdJLCBTQVZJIGFuZCBFVkkNCg0KSGlzdG9ncmFtcyB0byBjaGVjayB0aGF0IHZhbHVlcyBhcmUgb2s6DQoNCmBgYHtyfQ0KaGlzdF9ORFZJIDwtIHBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRWSV9tYXgiLCAiTkRWSSBtYXgiKQ0KaGlzdF9ORE1JIDwtIHBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRNSV9tYXgiLCAiTkRNSSBtYXgiKQ0KaGlzdF9ORFdJIDwtIHBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRXSV9tYXgiLCAiTkRXSSBtYXgiKQ0KaGlzdF9TQVZJIDwtIHBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiU0FWSV9tYXgiLCAiU0FWSSBtYXgiKQ0KaGlzdF9FVkkgPC0gcGxvdF9oaXN0b2dyYW0oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJFVklfbWF4IiwgIkVWSSBtYXgiKQ0KaGlzdF9ORFZJDQpoaXN0X05ETUkNCmhpc3RfTkRXSQ0KaGlzdF9TQVZJDQpoaXN0X0VWSSAjIFNvbWUgdmFsdWVzIHdyb25nIQ0KaGlzdF9FVklfMSA8LSBwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKEVWSV9tYXggPD0gMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFVklfbWF4IiwgIkVWSSBtYXgiKQ0KaGlzdF9FVklfMQ0KYGBgDQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgIGZpbHRlcihFVklfbWF4ID4gMSkpDQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpJT4lDQogIGZpbHRlcihFVklfbWF4ID4gMSkgJT4lDQogIGNvdW50KGJpb2dlbywgdW5pdCkNCmBgYA0KDQpTbyBmYXIsIHJlbW92ZSBvYnNlcnZhdGlvbnMgd2l0aCBFVklfbWF4ID4gMSBmcm9tIHN0dWZmIHVzaW5nIEVWSSB2YWx1ZXMuIA0KDQpEaXN0cmlidXRpb24gcGxvdHM6DQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgICBjKCJORE1JX21heCIsICJORE1JX3A5MCIsICJORE1JX21pbiIsICJORE1JX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRNSSBtYXgiLCAiTkRNSSBwOTAiLCAiTkRNSSBtaW4iLCAiTkRNSSBwMTAiKSkNCmRpc3RyX3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgIGMoIk5EV0lfbWF4IiwgIk5EV0lfcDkwIiwgIk5EV0lfbWluIiwgIk5EV0lfcDEwIiksIA0KICAgICAgICAgICBjKCJORFdJIG1heCIsICJORFdJIHA5MCIsICJORFdJIG1pbiIsICJORFdJIHAxMCIpKQ0KZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgYygiU0FWSV9tYXgiLCAiU0FWSV9wOTAiLCAiU0FWSV9taW4iLCAiU0FWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIlNBVkkgbWF4IiwgIlNBVkkgcDkwIiwgIlNBVkkgbWluIiwgIlNBVkkgcDEwIikpDQpkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSBmaWx0ZXIoRVZJX21heCA8PSAxKSwNCiAgICAgICAgICAgYygiRVZJX21heCIsICJFVklfcDkwIiwgIkVWSV9taW4iLCAiRVZJX3AxMCIpLCANCiAgICAgICAgICAgYygiRVZJIG1heCIsICJFVkkgcDkwIiwgIkVWSSBtaW4iLCAiRVZJIHAxMCIpKQ0KYGBgDQoNCiMjIENIDQoNCmBgYHtyfQ0KcGxvdF9kaXN0cl9DSCA8LSBkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiY2Fub3B5X2hlaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNhbm9weSBoZWlnaHQgKG0pIikNCnBsb3RfZGlzdHJfQ0gNCmBgYA0KIA0KIyMjIFNob3cgaGFiaXRhdHMgd2l0aCBDSCBjYXRlZ29yaWVzDQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgIyBLZWVwIG9ubHkgZm9yZXN0cywgZ3Jhc3NsYW5kcywgc2hydWJsYW5kcyBhbmQgd2V0bGFuZHMNCiAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgICBtdXRhdGUoQ0hfY2F0ID0NCiAgICAgICAgICAgICAgICAgIGZhY3RvcigNCiAgICAgICAgICAgICAgICAgICAgY2FzZV93aGVuKGNhbm9weV9oZWlnaHQgPT0gMCB+ICIwIG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDAgJiBjYW5vcHlfaGVpZ2h0IDw9IDEgfiAiMC0xIG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDEgJiBjYW5vcHlfaGVpZ2h0IDw9MiB+ICIxLTIgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMiAmIGNhbm9weV9oZWlnaHQgPD01IH4gIjItNSBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiA1ICYgY2Fub3B5X2hlaWdodCA8PTggfiAiNS04IG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDggfiAiPiA4IG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoY2Fub3B5X2hlaWdodCkgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygNCiAgICAgICAgICAgICAgICAgICAgICAiMCBtIiwgIjAtMSBtIiwgIjEtMiBtIiwgIjItNSBtIiwgIjUtOCBtIiwgIj4gOCBtIikpKSwNCiAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCBmaWxsID0gQ0hfY2F0KSkgKw0KICBnZW9tX2JhcigpICsgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBsYWJlbF9udW1iZXIoKSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSkgKw0KICBsYWJzKHggPSAiRVVOSVMgbGV2ZWwgMSIsIGZpbGwgPSAiQ2Fub3B5IGhlaWdodCIpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOCwgMC43NSksDQogICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiKQ0KYGBgDQoNCiMjIyBTdGF0cyBwZXIgaGFiaXRhdCB0eXBlDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGdyb3VwX2J5KEVVTklTYV8xX2Rlc2NyKSAlPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhjYW5vcHlfaGVpZ2h0LCBsaXN0KA0KICAgIG1lYW4gPSBtZWFuLA0KICAgIG1lZGlhbiA9IG1lZGlhbiwNCiAgICBzZCA9IHNkLA0KICAgIG1pbiA9IG1pbiwNCiAgICBtYXggPSBtYXgNCiAgICApLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQoNCiMjIFBoZW5vbG9neQ0KDQojIyMgQ2FsY3VsYXRlIG1ldHJpY3MNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgbXV0YXRlKA0KICAgICMgRGlmZmVyZW5jZSBORFZJIGJldHdlZW4gUGVhayBhbmQgU09TDQogICAgZGlmZl9QZWFrX1NPUyA9IE5EVklfYXRfUGVhayAtIE5EVklfYXRfU09TLA0KICAgICMgRGlmZmVyZW5jZSBORFZJIGJldHdlZW4gUGVhayBhbmQgRU9TDQogICAgZGlmZl9QZWFrX0VPUyA9IE5EVklfYXRfUGVhayAtIE5EVklfYXRfRU9TKQ0KYGBgDQoNCiMjIyBIaXN0b2dyYW1zIHBoZW5vbG9neSBtZWFzdXJlcw0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpICYgUzJfcGhlbl9kYXRhID09IFQpICU+JQ0KICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKFNPU19ET1ksIFBlYWtfRE9ZLCBFT1NfRE9ZKSwgbmFtZXNfdG8gPSAibmFtZSIsDQogICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIiksDQogICAgICAgYWVzKHggPSB2YWx1ZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBmYWNldF9ncmlkKGJpb2dlbyB+IG5hbWUsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIHRoZW1lX2J3KCkNCmdncGxvdChkYXRhID0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpICYgUzJfcGhlbl9kYXRhID09IFQpICU+JQ0KICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKE5EVklfYXRfU09TLCBORFZJX2F0X1BlYWssIE5EVklfYXRfRU9TKSwNCiAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJuYW1lIiwgdmFsdWVzX3RvID0gInZhbHVlIiksDQogICAgICAgYWVzKHggPSB2YWx1ZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBmYWNldF9ncmlkKGJpb2dlbyB+IG5hbWUsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIHRoZW1lX2J3KCkNCmdncGxvdChkYXRhID0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpICYgUzJfcGhlbl9kYXRhID09IFQpICU+JQ0KICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKGRpZmZfUGVha19TT1MsIGRpZmZfUGVha19FT1MpLA0KICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm5hbWUiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSwNCiAgICAgICBhZXMoeCA9IHZhbHVlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGZhY2V0X2dyaWQoYmlvZ2VvIH4gbmFtZSwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgdGhlbWVfYncoKQ0KZ2dwbG90KGRhdGEgPSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikgJiBTMl9waGVuX2RhdGEgPT0gVCkgJT4lDQogICAgICAgICBwaXZvdF9sb25nZXIoY29scyA9IGMoU2Vhc29uX0xlbmd0aCksDQogICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibmFtZSIsIHZhbHVlc190byA9ICJ2YWx1ZSIpLA0KICAgICAgIGFlcyh4ID0gdmFsdWUpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpICsNCiAgZmFjZXRfZ3JpZChiaW9nZW8gfiBuYW1lLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyMjIERpc3RyaWJ1dGlvbnMNCg0KYGBge3J9DQpwbG90X2Rpc3RyX1NPU19ET1kgPC0gZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIlNPU19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNPUyBET1kiKQ0KcGxvdF9kaXN0cl9QZWFrX0RPWSA8LSBkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiUGVha19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrIERPWSIpDQpwbG90X2Rpc3RyX0VPU19ET1kgPC0gZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkVPUyBET1kiKQ0KcGxvdF9kaXN0cl9ORFZJX2F0X1NPUyA8LSBkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRWSV9hdF9TT1MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORFZJIGF0IFNPUyIpDQpwbG90X2Rpc3RyX05EVklfYXRfUGVhayA8LSBkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRWSV9hdF9QZWFrIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVkkgYXQgUGVhayIpDQpwbG90X2Rpc3RyX05EVklfYXRfRU9TIDwtIGRpc3RyX3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJORFZJX2F0X0VPUyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVkkgYXQgRU9TIikNCnBsb3RfZGlzdHJfZGlmZl9QZWFrX1NPUyA8LSBkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiZGlmZl9QZWFrX1NPUyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRGlmZmVyZW5jZSBQZWFrLVNPUyIpDQpwbG90X2Rpc3RyX2RpZmZfUGVha19FT1MgPC0gZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgImRpZmZfUGVha19FT1MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkRpZmZlcmVuY2UgUGVhay1FT1MiKQ0KcGxvdF9kaXN0cl9TZWFzb25fTGVuZ3RoIDwtIGRpc3RyX3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJTZWFzb25fTGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTZWFzb24gTGVuZ3RoIikNCnBsb3RfZGlzdHJfU09TX0RPWQ0KcGxvdF9kaXN0cl9QZWFrX0RPWQ0KcGxvdF9kaXN0cl9FT1NfRE9ZDQpwbG90X2Rpc3RyX05EVklfYXRfU09TDQpwbG90X2Rpc3RyX05EVklfYXRfUGVhaw0KcGxvdF9kaXN0cl9ORFZJX2F0X0VPUw0KcGxvdF9kaXN0cl9kaWZmX1BlYWtfU09TDQpwbG90X2Rpc3RyX2RpZmZfUGVha19FT1MNCnBsb3RfZGlzdHJfU2Vhc29uX0xlbmd0aA0KYGBgDQoNCiMgRGlzdHJpYnV0aW9ucyBwZXIgYmlvcmVnaW9uDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjcmVhdGUgcGxvdHMgd2l0aCB2aW9saW4gKyBib3hwbG90ICsgcG9pbnRzDQpkaXN0cl9wbG90X2Jpb2dlbyA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhciwgeV9sYWJlbCkgew0KICBnZ3Bsb3QoZGF0YSA9IGRhdGEgJT4lDQogICAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSksDQogICAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gISFzeW0oeV92YXIpLCBmaWxsID0gRVVOSVNhXzFfZGVzY3IpKSArDQogICAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgpICsNCiAgICBnZW9tX3BvaW50KGFlcyh5ID0gISFzeW0oeV92YXIpLCBjb2xvciA9IEVVTklTYV8xX2Rlc2NyKSwNCiAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogICAgbGFicyh5ID0geV9sYWJlbCwgeCA9ICJFVU5JU2FfMV9kZXNjciIpICsNCiAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICAgIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkgKyBmYWNldF93cmFwKH4gYmlvZ2VvKQ0KfQ0KYGBgDQoNCiMjIE1heC4gTkRWSSwgTkRNSSwgTkRXSSwgU0FWSSBhbmQgRVZJDQoNCkRpc3RyaWJ1dGlvbiBwbG90czoNCg0KYGBge3J9DQpwbG90X2Rpc3RyX05EVklfYmlvZ2VvIDwtIGRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORFZJX21heCIsICJORFZJIG1heCIpDQpwbG90X2Rpc3RyX05ETUlfYmlvZ2VvIDwtIGRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORE1JX21heCIsICJORE1JIG1heCIpDQpwbG90X2Rpc3RyX05EV0lfYmlvZ2VvIDwtIGRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORFdJX21heCIsICJORFdJIG1heCIpDQpwbG90X2Rpc3RyX1NBVklfYmlvZ2VvIDwtIGRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTQVZJX21heCIsICJTQVZJIG1heCIpDQpwbG90X2Rpc3RyX0VWSV9iaW9nZW8gPC0gZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihFVklfbWF4IDw9IDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFVklfbWF4IiwgIkVWSSBtYXgiKQ0KcGxvdF9kaXN0cl9ORFZJX2Jpb2dlbw0KcGxvdF9kaXN0cl9ORE1JX2Jpb2dlbw0KcGxvdF9kaXN0cl9ORFdJX2Jpb2dlbw0KcGxvdF9kaXN0cl9TQVZJX2Jpb2dlbw0KcGxvdF9kaXN0cl9FVklfYmlvZ2VvDQpgYGANCg0KQk9SIG1pc3NpbmcgYmVjYXVzZSB0aGVyZSBpcyBubyBFVU5JUyBpbmZvIGZvciB0aG9zZSB0aGF0IGhhdmUgUlMgaW5mby4NCg0KIyMgQ0gNCg0KYGBge3J9DQpwbG90X2Rpc3RyX0NIX2Jpb2dlbyA8LSBkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYW5vcHlfaGVpZ2h0IiwgIkNhbm9weSBoZWlnaHQgKG0pIikNCnBsb3RfZGlzdHJfQ0hfYmlvZ2VvDQpgYGANCg0KSW4gdGhpcyBwbG90LCB0aG9zZSB3aXRoIGJpb2dlbyA9IE5BIGFyZSB0aG9zZSB0aGF0IGRvIG5vdCBoYXZlIFMyIG9yIExhbmRzYXQgZGF0YSAoYW5kIHRodXMgYmlvZ2VvIGhhcyBub3QgYmVlbiBhc3NpZ25lZCksIGJ1dCBoYXZlIENIIGRhdGEuIFdlIHNob3VsZCBsYXRlciBhc3NpZ24gYSBiaW9nZW8gYmFzZWQgb24gbG9jYXRpb24uIA0KDQojIyBQaGVub2xvZ3kNCg0KYGBge3J9DQpwbG90X2Rpc3RyX1NPU19ET1lfYmlvZ2VvIDwtIGRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTT1NfRE9ZIiwgIlNPUyBET1kiKQ0KcGxvdF9kaXN0cl9QZWFrX0RPWV9iaW9nZW8gPC0gZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrX0RPWSIsICJQZWFrIERPWSIpDQpwbG90X2Rpc3RyX0VPU19ET1lfYmlvZ2VvIDwtIGRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFT1NfRE9ZIiwgIkVPUyBET1kiKQ0KcGxvdF9kaXN0cl9ORFZJX2F0X1NPU19iaW9nZW8gPC0gZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVklfYXRfU09TIiwgIk5EVkkgYXQgU09TIikNCnBsb3RfZGlzdHJfTkRWSV9hdF9QZWFrX2Jpb2dlbyA8LSBkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoIWlzLm5hKGJpb2dlbykpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVklfYXRfUGVhayIsICJORFZJIGF0IFBlYWsiKQ0KcGxvdF9kaXN0cl9ORFZJX2F0X0VPU19iaW9nZW8gPC0gZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVklfYXRfRU9TIiwgIk5EVkkgYXQgRU9TIikNCnBsb3RfZGlzdHJfZGlmZl9QZWFrX1NPU19iaW9nZW8gPC0gZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRpZmZfUGVha19TT1MiLCAiRGlmZmVyZW5jZSBQZWFrLVNPUyIpDQpwbG90X2Rpc3RyX2RpZmZfUGVha19FT1NfYmlvZ2VvIDwtIGRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJkaWZmX1BlYWtfRU9TIiwgIkRpZmZlcmVuY2UgUGVhay1FT1MiKQ0KcGxvdF9kaXN0cl9TZWFzb25fTGVuZ3RoX2Jpb2dlbyA8LSBkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoIWlzLm5hKGJpb2dlbykpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU2Vhc29uX0xlbmd0aCIsICJTZWFzb24gTGVuZ3RoIikNCg0KcGxvdF9kaXN0cl9TT1NfRE9ZX2Jpb2dlbw0KcGxvdF9kaXN0cl9QZWFrX0RPWV9iaW9nZW8NCnBsb3RfZGlzdHJfRU9TX0RPWV9iaW9nZW8NCnBsb3RfZGlzdHJfTkRWSV9hdF9TT1NfYmlvZ2VvDQpwbG90X2Rpc3RyX05EVklfYXRfUGVha19iaW9nZW8NCnBsb3RfZGlzdHJfTkRWSV9hdF9FT1NfYmlvZ2VvDQpwbG90X2Rpc3RyX2RpZmZfUGVha19TT1NfYmlvZ2VvDQpwbG90X2Rpc3RyX2RpZmZfUGVha19FT1NfYmlvZ2VvDQpwbG90X2Rpc3RyX1NlYXNvbl9MZW5ndGhfYmlvZ2VvDQpgYGANCg0KQk9SIG1pc3NpbmcgYmVjYXVzZSB0aGVyZSBpcyBubyBFVU5JUyBpbmZvIGZvciB0aG9zZSB0aGF0IGhhdmUgUlMgaW5mby4NCg0KIyBIRVJFOiBWZXJpZnkgU09TLVBlYWtfRU9TIE9EWQ0KDQpFUlJPUlMhIERvdWJsZS1jaGVjayBhbmQgc2VuZCB0byBCZWE6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihTT1NfRE9ZID4gUGVha19ET1kpDQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKFBlYWtfRE9ZID4gRU9TX0RPWSkNCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSBmaWx0ZXIoU09TX0RPWSA+IEVPU19ET1kpDQpgYGANCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihORFZJX2F0X1BlYWsgPCBORFZJX2F0X1NPUykNCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSBmaWx0ZXIoTkRWSV9hdF9QZWFrIDwgTkRWSV9hdF9FT1MpDQpgYGANCg0KIyBDb21wYXJpc29uIGRpZmZlcmVudGlhbCBHUFMgdnMuIG90aGVyIHBvaW50cw0KDQpgYGB7cn0NCiMgRGVmaW5lIGZ1bmN0aW9uDQpkaXN0cl9wbG90X0dQUyA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhciwgeV9sYWJlbCkgew0KICBnZ3Bsb3QoZGF0YSA9IGRhdGEgJT4lDQogICAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgICAgIG11dGF0ZShHUFMgPSANCiAgICAgICAgICAgICAgICAgICAgaWZlbHNlKA0KICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKGBMb2NhdGlvbiBtZXRob2RgKSB8DQogICAgICAgICAgICAgICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCAhPSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiwNCiAgICAgICAgICAgICAgICAgICAgICAiT3RoZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICJEaWZmZXJlbnRpYWwgR1BTIikpLA0KICAgICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9ICEhc3ltKHlfdmFyKSwgZmlsbCA9IEVVTklTYV8xX2Rlc2NyKSkgKw0KICAgIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44KSArDQogICAgZ2VvbV9wb2ludChhZXMoeSA9ICEhc3ltKHlfdmFyKSwgY29sb3IgPSBFVU5JU2FfMV9kZXNjciksDQogICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogICAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogICAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogICAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICAgIGxhYnMoeSA9IHlfbGFiZWwsIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsNCiAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICAgIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkgKyBmYWNldF93cmFwKH4gR1BTKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdF9kaXN0cl9ORFZJX0dQUyA8LSBkaXN0cl9wbG90X0dQUyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVklfbWF4IiwgIk5EVkkgbWF4IikNCnBsb3RfZGlzdHJfTkRNSV9HUFMgPC0gZGlzdHJfcGxvdF9HUFMoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORE1JX21heCIsICJORE1JIG1heCIpDQpwbG90X2Rpc3RyX05EV0lfR1BTIDwtIGRpc3RyX3Bsb3RfR1BTKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRXSV9tYXgiLCAiTkRXSSBtYXgiKQ0KcGxvdF9kaXN0cl9TQVZJX0dQUyA8LSBkaXN0cl9wbG90X0dQUyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNBVklfbWF4IiwgIlNBVkkgbWF4IikNCnBsb3RfZGlzdHJfRVZJX0dQUyA8LSBkaXN0cl9wbG90X0dQUyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihFVklfbWF4IDw9IDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRVZJX21heCIsICJFVkkgbWF4IikNCnBsb3RfZGlzdHJfTkRWSV9HUFMNCnBsb3RfZGlzdHJfTkRNSV9HUFMNCnBsb3RfZGlzdHJfTkRXSV9HUFMNCnBsb3RfZGlzdHJfU0FWSV9HUFMNCnBsb3RfZGlzdHJfRVZJX0dQUw0KYGBgDQoNCiMgSW5jbHVkaW5nIFBMT1QgKFVTRSBMQVRFUj8pDQoNClN1bW1hcml6ZSB2YXJpYWJsZXMgYnkgcGxvdDoNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF9zdW1tIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQpICU+JQ0KICBncm91cF9ieShQTE9UKSAlPiUNCiAgc3VtbWFyaXplKEVVTklTMSA9IGlmX2Vsc2Uobl9kaXN0aW5jdChFVU5JU2FfMSkgPiAxLCAiQ2hhbmdlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5pcXVlKEVVTklTYV8xKVsxXSksDQogICAgICAgICAgICBjb3VudCA9IG4oKSwNCiAgICAgICAgICAgIGFjcm9zcyhzdGFydHNfd2l0aCgiTkRWSSIpLCBsaXN0KG1lYW4gPSBtZWFuLCBzZCA9IHNkKSwNCiAgICAgICAgICAgICAgICAgICAubmFtZXMgPSAie2NvbH1fe2ZufSIpKQ0KDQpgYGANCg0KTWF5YmUgdXNlIGxhdGVyIGJlY2F1c2Ugbm93IG1hbnkgcGxvdHMgaGF2ZSBvbmx5IG9uZSBvYnNlcnZhdGlvbiwgcHJvYmFibHkgYmVjYXVzZSBzb21lIExhbmRzYXQgZGF0YSBpcyBtaXNzaW5nPw0KDQojIEZpcnN0IHZhbGlkYXRpb24NCg0KRm9yIFQsIFIsIFMsIFEgaGFiaXRhdHMuDQoNCkRlZmluZSBhIHNldCBvZiBydWxlcyBmb3IgYSBmaXJzdCB2YWxpZGF0aW9uIG9mIEFMTCBSZVN1cnZleSBkYXRhLiBXZSBjYW4gY2FsbCB0aGVzZSAiRXhwZXJ0LWJhc2VkIiBydWxlcy4NCg0KTnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBSZVN1cnZleSBmcm9tIHRoZSBoYWJpdGF0cyBvZiBpbnRlcmVzdDoNCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkpDQpgYGANCg0KTnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBSZVN1cnZleSBmcm9tIHRoZSBoYWJpdGF0cyBvZiBpbnRlcmVzdCBhbmQgd2l0aCBhbGwgUlMgZGF0YToNCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgZmlsdGVyKENIX2RhdGEgPT0gVCkgJT4lDQogICAgICAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PVQpICU+JQ0KICAgICAgIGZpbHRlcihTMl9waGVuX2RhdGEgPT0gVCkpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKQ0KYGBgDQoNCiMjIERlZmluZSBydWxlcw0KDQpDcmVhdGUgY29sdW1uIGZvciBmaXJzdCB2YWxpZGF0aW9uIGJhc2VkIG9uIGRpZmZlcmVudCBpbmRpY2F0b3JzLCB3aGVyZSAid3JvbmciIGlzIG5vdGVkIHdoZW4gdGhlIHZhbGlkYXRpb24gcnVsZSBpcyBub3QgbWV0LiBJbmNsdWRlIEVVTklTMSBjb25mdXNpb25zLg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JSBjb3VudChFVU5JU2FfMSwgRVVOSVMxX2NvbmZfdHlwZSkNCmBgYA0KDQpEZWZpbmUgcnVsZXM6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgPC0NCiAgZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQogIG11dGF0ZSgNCiAgICB2YWxpZF8xX05EV0kgPSBjYXNlX3doZW4oDQogICAgICAjIFBvaW50cyB0aGF0IGFyZSBiYXNpY2FsbHkgd2F0ZXINCiAgICAgIE5EV0lfbWF4ID4gMC4zIH4gIndyb25nIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICB2YWxpZF8xX0NIID0gY2FzZV93aGVuKA0KICAgICAgIyBUIHBvaW50cyB3aXRoIGxvdyBDSA0KICAgICAgRVVOSVNhXzEgPT0gIlQiICYgY2Fub3B5X2hlaWdodCA8IDggfiAid3JvbmciLA0KICAgICAgIyBTIHBvaW50cyB3aXRoIGxvdyBDSA0KICAgICAgRVVOSVNhXzEgPT0iUyIgJiBjYW5vcHlfaGVpZ2h0IDwgNSB+ICJ3cm9uZyIsDQogICAgICAjIFIgJiBRIHBvaW50cyB3aXRoIGhpZ2ggQ0gNCiAgICAgIEVVTklTYV8xICVpbiUgYygiUiIsICJRIikgJiBjYW5vcHlfaGVpZ2h0ID4gMiB+ICJ3cm9uZyIsDQogICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXyksDQogICAgdmFsaWRfMV9ORFZJID0gY2FzZV93aGVuKA0KICAgICAgIyBUIHBvaW50cyB3aXRoIGxvdyBORFZJX21heA0KICAgICAgRVVOSVNhXzEgPT0gIlQiICYgTkRWSV9tYXggPCAwLjYgfiAid3JvbmciLA0KICAgICAgIyBTLVItUSBwb2ludHMgd2l0aCBsb3cgTkRWSV9tYXgNCiAgICAgIEVVTklTYV8xICVpbiUgYygiUiIsICJTIiwgIlEiKSAmIE5EVklfbWF4IDwgMC4yIH4gIndyb25nIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICAjIENvdW50IGhvdyBtYW55IHZhbGlkYXRpb24gcnVsZXMgYXJlIG5vdCBtZXQNCiAgICB2YWxpZF8xX2NvdW50ID0gcm93U3VtcyhhY3Jvc3MoYyh2YWxpZF8xX05EV0ksIHZhbGlkXzFfQ0gsIHZhbGlkXzFfTkRWSSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+IC4gPT0gIndyb25nIiksIG5hLnJtID0gVFJVRSksDQogICAgIyBQb2ludHMgd2hlcmUgYXQgbGVhc3QgMSBydWxlIG5vdCBtZXQNCiAgICB2YWxpZF8xID0gaWZfZWxzZSh2YWxpZF8xX2NvdW50ID4gMCwgIkF0IGxlYXN0IDEgcnVsZSBicm9rZW4iLA0KICAgICAgICAgICAgICAgICAgICAgICJObyBydWxlcyBicm9rZW4gc28gZmFyIikNCiAgICApDQpgYGANCg0KIyMgUGxvdHMgZmlyc3QgdmFsaWRhdGlvbg0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCU+JQ0KICAgICAgICAgbXV0YXRlKHJ1bGVzX2Jyb2tlbiA9IGNhc2Vfd2hlbigNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgfiAiTkRXSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciIH4gIk5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyIgfiAiQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9ORFZJID09ICJ3cm9uZyJ+ICJORFdJICsgTkRWSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMiAmDQogICAgICAgICAgICAgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyJ+ICJORFdJICsgQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRWSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAzIH4gIk5EV0kgKyBORFZJICsgQ0giLA0KICAgICAgICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICAgICAgKSksIA0KICAgICAgIGFlcyh4ID0gdmFsaWRfMV9jb3VudCwgZmlsbCA9IHJ1bGVzX2Jyb2tlbikpICsNCiAgZ2VvbV9iYXIoKSArIGxhYnMoeCA9ICJOdW1iZXIgb2YgYnJva2VuIHJ1bGVzIikNCmBgYA0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICAgICAgICAgbXV0YXRlKHJ1bGVzX2Jyb2tlbiA9IGNhc2Vfd2hlbigNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgfiAiTkRXSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciIH4gIk5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyIgfiAiQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9ORFZJID09ICJ3cm9uZyJ+ICJORFdJICsgTkRWSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMiAmDQogICAgICAgICAgICAgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyJ+ICJORFdJICsgQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRWSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAzIH4gIk5EV0kgKyBORFZJICsgQ0giLA0KICAgICAgICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICAgICAgKSkgJT4lDQogIGNvdW50KHJ1bGVzX2Jyb2tlbiwgRVVOSVMxX2NvbmZfdHlwZSkNCmBgYA0KDQpQcm9wb3J0aW9uIG9mIG9ic2VydmF0aW9ucyBub3QgdmFsaWRhdGVkIChzbyBmYXIpOg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lIGZpbHRlcih2YWxpZF8xX2NvdW50ID4gMCkpLw0KICBucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsKQ0KYGBgDQoNCkJ1dCBiZSBhd2FyZSB0aGF0IHRoZXJlIGFyZSBzdGlsbCBtYW55IG1pc3NpbmcgUlMgZGF0YSwgZS5nLiBMYW5kc2F0IGRhdGEgZm9yIGJpb3JlZ2lvbiBDT04gKG1hbnkgcG9pbnRzKS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQogICAgICAgICBtdXRhdGUoZGlmZl9HUFMgPSBpZl9lbHNlKA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCAhPSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8DQogICAgICAgICAgICAgaXMubmEoYExvY2F0aW9uIG1ldGhvZGApLCAibm8iLCAieWVzIikpLCANCiAgICAgICBhZXMoeCA9IGRpZmZfR1BTLCBmaWxsID0gdmFsaWRfMSkpICsNCiAgZ2VvbV9iYXIoKSArIGxhYnMoeCA9ICJEaWZmZXJlbnRpYWwgR1BTIikNCmdncGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShHUFMgPSBjYXNlX3doZW4oDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIH4gInllcyIsDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfiAieWVzIiwNCiAgICAgICAgICAgaXMubmEoYExvY2F0aW9uIG1ldGhvZGApIH4gIm5vIiwNCiAgICAgICAgICAgVFJVRSB+ICJubyINCiAgICAgICAgICkpLCANCiAgICAgICBhZXMoeCA9IEdQUywgZmlsbCA9IHZhbGlkXzEpKSArDQogIGdlb21fYmFyKCkgKyBsYWJzKHggPSAiR1BTIikNCmBgYA0KDQpQb2ludHMgd2l0aCBhbnkgcnVsZSBicm9rZW4gYW5kIGNvbmZ1c2lvbiBiZXR3ZWVuIEVVTklTOg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQogICAgICAgZmlsdGVyKEVVTklTMV9jb25mID09IFQgJiB2YWxpZF8xX2NvdW50ID4gMCkpDQpgYGANCg0KQ29udmVydCB0byBzaHAgdG8gbG9vayBhdCB0aGVzZSBpbiBHSVM6DQoNCmBgYHtyfQ0KIyBzdF93cml0ZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiMgICAgICAgICAgICBmaWx0ZXIoRVVOSVMxX2NvbmYgPT0gVCAmIHZhbGlkXzFfY291bnQgPiAwKSAlPiUNCiMgICAgICAgICAgICBzdF9hc19zZihjb29yZHMgPSBjKCJMb25fdXBkYXRlZCIsICJMYXRfdXBkYXRlZCIpLCBjcnMgPSA0MzI2KSwNCiMgICAgICAgICAgIkM6L0dJUy9NT1RJVkFURS9zaGFwZWZpbGVzL3Jlc3Vydl9ub3RfdmFsX0VVTklTX2NvbmYuc2hwIikNCmBgYA0KDQpDaGVja2VkIGFuZCB5ZXMNCg0KSG93IG1hbnkgcG9pbnRzIHdpdGggZGlmZmVyZW50aWFsIEdQUyB0aGF0IGhhdmUgYXQgbGVhc3QgMSBydWxlIGJyb2tlbj8NCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgJg0KICAgICAgICAgICB2YWxpZF8xID09ICJBdCBsZWFzdCAxIHJ1bGUgYnJva2VuIikpDQpgYGANCg0KQ29udmVydCB0byBzaHAgdG8gbG9vayBhdCB0aGVzZSBpbiBHSVM6DQoNCmBgYHtyfQ0KIyBzdF93cml0ZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiMgICAgICAgICAgICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgJg0KIyAgICAgICAgICAgICAgICAgICAgIHZhbGlkXzEgPT0gIkF0IGxlYXN0IDEgcnVsZSBicm9rZW4iKSAlPiUNCiMgICAgICAgICAgICBzdF9hc19zZihjb29yZHMgPSBjKCJMb25fdXBkYXRlZCIsICJMYXRfdXBkYXRlZCIpLCBjcnMgPSA0MzI2KSwNCiMgICAgICAgICAgIkM6L0dJUy9NT1RJVkFURS9zaGFwZWZpbGVzL3Jlc3Vydl9ub3RfdmFsX2RpZmZfR1BTLnNocCIpDQpgYGANCg0KIyBEaXN0cmlidXRpb25zIGZyb20gZGlmZiBHUFMgcG9pbnRzIHdpdGhvdXQgcnVsZXMgYnJva2VuIHNvIGZhcg0KDQpDcmVhdGUgdGliYmxlIHdpdGggZGlmZmVyZW50aWFsIEdQUyBwb2ludHMgd2l0aG91dCBydWxlcyBicm9rZW4gc28gZmFyOg0KDQpgYGB7cn0NCmRpZmZfR1BTX3ZhbGlkIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgJg0KICAgICAgICAgICB2YWxpZF8xID09ICJObyBydWxlcyBicm9rZW4gc28gZmFyIikgDQpgYGANCg0KIyMgTWF4LiBhbmQgbWluLiBORFZJLCBORE1JLCBORFdJLCBTQVZJIGFuZCBFVkkNCg0KYGBge3J9DQpwbG90X2Rpc3RyX05EVklfbWF4X2RpZmZfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoZGlmZl9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRWSV9tYXgiLCAiTkRWSSBtYXgiKQ0KcGxvdF9kaXN0cl9ORE1JX21heF9kaWZmX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGRpZmZfR1BTX3ZhbGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5ETUlfbWF4IiwgIk5ETUkgbWF4IikNCnBsb3RfZGlzdHJfTkRXSV9tYXhfZGlmZl9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChkaWZmX0dQU192YWxpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORFdJX21heCIsICJORFdJIG1heCIpDQpwbG90X2Rpc3RyX1NBVklfbWF4X2RpZmZfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoZGlmZl9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU0FWSV9tYXgiLCAiU0FWSSBtYXgiKQ0KcGxvdF9kaXN0cl9FVklfbWF4X2RpZmZfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoZGlmZl9HUFNfdmFsaWQgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKEVWSV9tYXggPD0gMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFVklfbWF4IiwgIkVWSSBtYXgiKQ0KcGxvdF9kaXN0cl9ORFZJX21heF9kaWZmX0dQU192YWxpZA0KcGxvdF9kaXN0cl9ORE1JX21heF9kaWZmX0dQU192YWxpZA0KcGxvdF9kaXN0cl9ORFdJX21heF9kaWZmX0dQU192YWxpZA0KcGxvdF9kaXN0cl9TQVZJX21heF9kaWZmX0dQU192YWxpZA0KcGxvdF9kaXN0cl9FVklfbWF4X2RpZmZfR1BTX3ZhbGlkDQpgYGANCg0KYGBge3J9DQpwbG90X2Rpc3RyX05EVklfbWluX2RpZmZfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoZGlmZl9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRWSV9taW4iLCAiTkRWSSBtaW4iKQ0KcGxvdF9kaXN0cl9ORE1JX21pbl9kaWZmX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGRpZmZfR1BTX3ZhbGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5ETUlfbWluIiwgIk5ETUkgbWluIikNCnBsb3RfZGlzdHJfTkRXSV9taW5fZGlmZl9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChkaWZmX0dQU192YWxpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORFdJX21pbiIsICJORFdJIG1pbiIpDQpwbG90X2Rpc3RyX1NBVklfbWluX2RpZmZfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoZGlmZl9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU0FWSV9taW4iLCAiU0FWSSBtaW4iKQ0KcGxvdF9kaXN0cl9FVklfbWluX2RpZmZfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoZGlmZl9HUFNfdmFsaWQgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU29tZSB2YWx1ZXMgd3JvbmchDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRVZJIHNob3VsZCBub3QgYmUgbG93ZXIgdGhhbiAtMQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoRVZJX21pbiA+PSAtMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRVZJX21pbiIsICJFVkkgbWluIikNCnBsb3RfZGlzdHJfTkRWSV9taW5fZGlmZl9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfTkRNSV9taW5fZGlmZl9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfTkRXSV9taW5fZGlmZl9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfU0FWSV9taW5fZGlmZl9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfRVZJX21pbl9kaWZmX0dQU192YWxpZA0KYGBgDQoNCiMjIENIDQoNCmBgYHtyfQ0KcGxvdF9kaXN0cl9DSF9kaWZmX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGRpZmZfR1BTX3ZhbGlkLCAiY2Fub3B5X2hlaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNhbm9weSBoZWlnaHQgKG0pIikNCnBsb3RfZGlzdHJfQ0hfZGlmZl9HUFNfdmFsaWQNCmBgYA0KDQojIyBQaGVub2xvZ3kNCg0KYGBge3J9DQpwbG90X2Rpc3RyX1NPU19ET1lfZGlmZl9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChkaWZmX0dQU192YWxpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTT1NfRE9ZIiwgIlNPUyBET1kiKQ0KcGxvdF9kaXN0cl9QZWFrX0RPWV9kaWZmX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGRpZmZfR1BTX3ZhbGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrX0RPWSIsICJQZWFrIERPWSIpDQpwbG90X2Rpc3RyX0VPU19ET1lfZGlmZl9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChkaWZmX0dQU192YWxpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFT1NfRE9ZIiwgIkVPUyBET1kiKQ0KcGxvdF9kaXN0cl9TT1NfRE9ZX2RpZmZfR1BTX3ZhbGlkDQpwbG90X2Rpc3RyX1BlYWtfRE9ZX2RpZmZfR1BTX3ZhbGlkDQpwbG90X2Rpc3RyX0VPU19ET1lfZGlmZl9HUFNfdmFsaWQNCmBgYA0KDQpOb3QgdmVyeSBjb25jbHVzaXZlLi4uDQoNCiMgRGlzdHJpYnV0aW9ucyBmcm9tIEdQUyAoZGlmZiBvciBub3QpIHBvaW50cyB3aXRob3V0IHJ1bGVzIGJyb2tlbiBzbyBmYXINCg0KQ3JlYXRlIHRpYmJsZSB3aXRoIGRpZmZlcmVudGlhbCBHUFMgcG9pbnRzIHdpdGhvdXQgcnVsZXMgYnJva2VuIHNvIGZhcjoNCg0KYGBge3J9DQphbGxfR1BTX3ZhbGlkIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICBmaWx0ZXIoKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwgDQogICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiICkgJg0KICAgICAgICAgICB2YWxpZF8xID09ICJObyBydWxlcyBicm9rZW4gc28gZmFyIikgDQpgYGANCg0KIyMgTWF4LiBhbmQgbWluLiBORFZJLCBORE1JLCBORFdJLCBTQVZJIGFuZCBFVkkNCg0KYGBge3J9DQpwbG90X2Rpc3RyX05EVklfbWF4X2FsbF9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVklfbWF4IiwgIk5EVkkgbWF4IikNCnBsb3RfZGlzdHJfTkRNSV9tYXhfYWxsX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRNSV9tYXgiLCAiTkRNSSBtYXgiKQ0KcGxvdF9kaXN0cl9ORFdJX21heF9hbGxfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORFdJX21heCIsICJORFdJIG1heCIpDQpwbG90X2Rpc3RyX1NBVklfbWF4X2FsbF9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNBVklfbWF4IiwgIlNBVkkgbWF4IikNCnBsb3RfZGlzdHJfRVZJX21heF9hbGxfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoRVZJX21heCA8PSAxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkVWSV9tYXgiLCAiRVZJIG1heCIpDQpwbG90X2Rpc3RyX05EVklfbWF4X2FsbF9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfTkRNSV9tYXhfYWxsX0dQU192YWxpZA0KcGxvdF9kaXN0cl9ORFdJX21heF9hbGxfR1BTX3ZhbGlkDQpwbG90X2Rpc3RyX1NBVklfbWF4X2FsbF9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfRVZJX21heF9hbGxfR1BTX3ZhbGlkDQpgYGANCg0KYGBge3J9DQpwbG90X2Rpc3RyX05EVklfbWluX2FsbF9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5EVklfbWluIiwgIk5EVkkgbWluIikNCnBsb3RfZGlzdHJfTkRNSV9taW5fYWxsX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRNSV9taW4iLCAiTkRNSSBtaW4iKQ0KcGxvdF9kaXN0cl9ORFdJX21pbl9hbGxfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJORFdJX21pbiIsICJORFdJIG1pbiIpDQpwbG90X2Rpc3RyX1NBVklfbWluX2FsbF9HUFNfdmFsaWQgPC0gZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNBVklfbWluIiwgIlNBVkkgbWluIikNCnBsb3RfZGlzdHJfRVZJX21pbl9hbGxfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTb21lIHZhbHVlcyB3cm9uZyENCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBFVkkgc2hvdWxkIG5vdCBiZSBsb3dlciB0aGFuIC0xDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihFVklfbWluID49IC0xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFVklfbWluIiwgIkVWSSBtaW4iKQ0KcGxvdF9kaXN0cl9ORFZJX21pbl9hbGxfR1BTX3ZhbGlkDQpwbG90X2Rpc3RyX05ETUlfbWluX2FsbF9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfTkRXSV9taW5fYWxsX0dQU192YWxpZA0KcGxvdF9kaXN0cl9TQVZJX21pbl9hbGxfR1BTX3ZhbGlkDQpwbG90X2Rpc3RyX0VWSV9taW5fYWxsX0dQU192YWxpZA0KYGBgDQoNCiMjIENIDQoNCmBgYHtyfQ0KcGxvdF9kaXN0cl9DSF9hbGxfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwgImNhbm9weV9oZWlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDYW5vcHkgaGVpZ2h0IChtKSIpDQpwbG90X2Rpc3RyX0NIX2FsbF9HUFNfdmFsaWQNCmBgYA0KDQojIyBQaGVub2xvZ3kNCg0KYGBge3J9DQpwbG90X2Rpc3RyX1NPU19ET1lfYWxsX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU09TX0RPWSIsICJTT1MgRE9ZIikNCnBsb3RfZGlzdHJfUGVha19ET1lfYWxsX0dQU192YWxpZCA8LSBkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBlYWtfRE9ZIiwgIlBlYWsgRE9ZIikNCnBsb3RfZGlzdHJfRU9TX0RPWV9hbGxfR1BTX3ZhbGlkIDwtIGRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFT1NfRE9ZIiwgIkVPUyBET1kiKQ0KcGxvdF9kaXN0cl9TT1NfRE9ZX2FsbF9HUFNfdmFsaWQNCnBsb3RfZGlzdHJfUGVha19ET1lfYWxsX0dQU192YWxpZA0KcGxvdF9kaXN0cl9FT1NfRE9ZX2FsbF9HUFNfdmFsaWQNCmBgYA0KDQojIENvbWJpbmVkIHBsb3QNCg0KYGBge3J9DQpncmlkLmFycmFuZ2UoDQogIHBsb3RfZGlzdHJfTkRWSV9tYXhfZGlmZl9HUFNfdmFsaWQsIHBsb3RfZGlzdHJfTkRWSV9tYXhfYWxsX0dQU192YWxpZCwNCiAgcGxvdF9kaXN0cl9ORE1JX21pbl9kaWZmX0dQU192YWxpZCwgcGxvdF9kaXN0cl9ORE1JX21pbl9hbGxfR1BTX3ZhbGlkLA0KICBuY29sID0gMikNCmBgYA0KDQojIERpZmYgR1BTIHZhbGlkIHBvaW50cyBhYm92ZSBwMjAgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluIGZvciBlYWNoIGhhYml0YXQNCg0KYGBge3J9DQpwZXJjZW50aWxlc19kaWZmX0dQUyA8LSBkaWZmX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUocGVyY2VudGlsZV8yMF9ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCBwcm9icyA9IDAuMjAsIG5hLnJtID0gVCksDQogICAgICAgICAgICBwZXJjZW50aWxlXzIwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSkNCg0KZGlmZl9HUFNfdmFsaWQgPC0gZGlmZl9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihwZXJjZW50aWxlc19kaWZmX0dQUywgYnkgPSAiRVVOSVNhXzEiKSAlPiUNCiAgbXV0YXRlKGNhdGVnb3J5X05EVklfbWF4ID0gY2FzZV93aGVuKA0KICAgIE5EVklfbWF4IDwgcGVyY2VudGlsZV8yMF9ORFZJX21heCB+ICJiZWxvd18yMHRoIiwNCiAgICBORFZJX21heCA+PSBwZXJjZW50aWxlXzIwX05EVklfbWF4IH4gImFib3ZlXzIwdGgiKSwNCiAgY2F0ZWdvcnlfTkRNSV9taW4gPSBjYXNlX3doZW4oDQogICAgTkRNSV9taW4gPCBwZXJjZW50aWxlXzIwX05ETUlfbWluIH4gImJlbG93XzIwdGgiLA0KICAgIE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gfiAiYWJvdmVfMjB0aCIpKQ0KDQpnZ3Bsb3QoZGF0YSA9IGRpZmZfR1BTX3ZhbGlkLA0KICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIHkgPSBORFZJX21heCkpICsNCiAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgsDQogICAgICAgICAgICAgICAgICAgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2F0ZWdvcnlfTkRWSV9tYXgpLA0KICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogIGxhYnMoeSA9ICJORFZJIG1heCIsIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsNCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJiZWxvd18yMHRoIiA9ICJncmV5IiwgImFib3ZlXzIwdGgiID0gImxpZ2h0Ymx1ZSIpKSArDQogIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkNCg0KZ2dwbG90KGRhdGEgPSBkaWZmX0dQU192YWxpZCwNCiAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gTkRNSV9taW4pKSArDQogIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44LA0KICAgICAgICAgICAgICAgICAgIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNhdGVnb3J5X05ETUlfbWluKSwNCiAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLCBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICBsYWJzKHkgPSAiTkRNSSBtaW4iLCB4ID0gIkVVTklTIGxldmVsIDEiKSArDQogIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiYmVsb3dfMjB0aCIgPSAiZ3JleSIsICJhYm92ZV8yMHRoIiA9ICJsaWdodGJsdWUiKSkgKw0KICB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpDQpgYGANCg0KIyBHUFMgKGRpZmYgb3Igbm90KSB2YWxpZCBwb2ludHMgYWJvdmUgcDIwIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbiBmb3IgZWFjaCBoYWJpdGF0DQoNCmBgYHtyfQ0KcGVyY2VudGlsZXNfYWxsX0dQUyA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBncm91cF9ieShFVU5JU2FfMSkgJT4lDQogIHN1bW1hcml6ZShwZXJjZW50aWxlXzIwX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgcHJvYnMgPSAwLjIwLCBuYS5ybSA9IFQpKQ0KDQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihwZXJjZW50aWxlc19hbGxfR1BTLCBieSA9ICJFVU5JU2FfMSIpICU+JQ0KICBtdXRhdGUoY2F0ZWdvcnlfTkRWSV9tYXggPSBjYXNlX3doZW4oDQogICAgTkRWSV9tYXggPCBwZXJjZW50aWxlXzIwX05EVklfbWF4IH4gImJlbG93XzIwdGgiLA0KICAgIE5EVklfbWF4ID49IHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggfiAiYWJvdmVfMjB0aCIpLA0KICBjYXRlZ29yeV9ORE1JX21pbiA9IGNhc2Vfd2hlbigNCiAgICBORE1JX21pbiA8IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gfiAiYmVsb3dfMjB0aCIsDQogICAgTkRNSV9taW4gPj0gcGVyY2VudGlsZV8yMF9ORE1JX21pbiB+ICJhYm92ZV8yMHRoIikpDQoNCmdncGxvdChkYXRhID0gYWxsX0dQU192YWxpZCwNCiAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gTkRWSV9tYXgpKSArDQogIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44LA0KICAgICAgICAgICAgICAgICAgIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNhdGVnb3J5X05EVklfbWF4KSwNCiAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLCBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICBsYWJzKHkgPSAiTkRWSSBtYXgiLCB4ID0gIkVVTklTIGxldmVsIDEiKSArDQogIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiYmVsb3dfMjB0aCIgPSAiZ3JleSIsICJhYm92ZV8yMHRoIiA9ICJsaWdodGJsdWUiKSkgKw0KICB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpDQoNCmdncGxvdChkYXRhID0gYWxsX0dQU192YWxpZCwNCiAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gTkRNSV9taW4pKSArDQogIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44LA0KICAgICAgICAgICAgICAgICAgIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNhdGVnb3J5X05ETUlfbWluKSwNCiAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLCBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICBsYWJzKHkgPSAiTkRNSSBtaW4iLCB4ID0gIkVVTklTIGxldmVsIDEiKSArDQogIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiYmVsb3dfMjB0aCIgPSAiZ3JleSIsICJhYm92ZV8yMHRoIiA9ICJsaWdodGJsdWUiKSkgKw0KICB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpDQpgYGANCg0KIyBSRiBtb2RlbHMNCg0KVXNpbmcgdGhlIGNvbmRpdGlvbmFsIGluZmVyZW5jZSB2ZXJzaW9uIG9mIHJhbmRvbSBmb3Jlc3QgKGNmb3Jlc3QgaW4gcGFja2FnZSBwYXJ0eSkuIFN1Z2dlc3RlZCBpZiB0aGUgZGF0YSBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQuIENmb3Jlc3QgaXMgbW9yZSBzdGFibGUgaW4gZGVyaXZpbmcgdmFyaWFibGUgaW1wb3J0YW5jZSB2YWx1ZXMgaW4gdGhlIHByZXNlbmNlIG9mIGhpZ2hseSBjb3JyZWxhdGVkIHZhcmlhYmxlcywgdGh1cyBwcm92aWRpbmcgYmV0dGVyIGFjY3VyYWN5IGluIGNhbGN1bGF0aW5nIHZhcmlhYmxlIGltcG9ydGFuY2UgKHJlZiBiZWxvdykuDQoNCkhvdGhvcm4sIFQuLCBIb3JuaWssIEsuIGFuZCBaZWlsZWlzLCBBLiAoMjAwNikgVW5iaWFzZWQgUmVjdXJzaXZlIFBvcnRpb25pbmc6IEEgQ29uZGl0aW9uYWwgSW5mZXJlbmNlIEZyYW1ld29yay4gSm91cm5hbCBvZiBDb21wdXRhdGlvbmFsIGFuZCBHcmFwaGljYWwgU3RhdGlzdGljcywgMTUsIDY1MS0NCjY3NC4gaHR0cDovL2R4LmRvaS5vcmcvMTAuMTE5OC8xMDYxODYwMDZYMTMzOTMzDQoNCiMjIEFsbCBHUFMgcG9pbnRzIGFib3ZlIHAyMA0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyBhYm92ZSBwMjAgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgc2VsZWN0KC1wZXJjZW50aWxlXzIwX05EVklfbWF4LCAtcGVyY2VudGlsZV8yMF9ORE1JX21pbikNCmBgYA0KDQpgYGB7cn0NCnBlcmNlbnRpbGVzIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC4yMCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzIwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIDAuMjAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV84MF9ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCAwLjgwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfODBfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgMC44MCwgbmEucm0gPSBUKQ0KICAgICkNCg0KIyBKb2luIHRoZSBwZXJjZW50aWxlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCBkYXRhDQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihwZXJjZW50aWxlcywgYnkgPSAiRVVOSVNhXzEiKQ0KDQojIEZpbHRlciByb3dzIGFib3ZlIHRoZSAyMHRoIHBlcmNlbnRpbGUgZm9yIGJvdGggdmFyaWFibGVzIGZvciBlYWNoIGNhdGVnb3J5IG9mIEVVTklTYV8xDQpmaWx0ZXJlZF9kYXRhMSA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBmaWx0ZXIoDQogICAgTkRWSV9tYXggPj0gcGVyY2VudGlsZV8yMF9ORFZJX21heCAmIE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4NCiAgICApICU+JQ0KICBmaWx0ZXIoIWlzLm5hKE5EVklfbWF4KSAmICFpcy5uYShORE1JX21heCkgJiAhaXMubmEoTkRXSV9tYXgpICYNCiAgICAgICAgICAgIWlzLm5hKFNBVklfbWF4KSAmICFpcy5uYShFVklfbWF4KSAmICFpcy5uYShORFZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoTkRNSV9taW4pICYgIWlzLm5hKE5EV0lfbWluKSAmICFpcy5uYShTQVZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoRVZJX21pbikpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczEgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhMSksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTEpKQ0KdHJhaW5fZGF0YTEgPC0gZmlsdGVyZWRfZGF0YTFbdHJhaW5faW5kaWNlczEsIF0NCnRlc3RfZGF0YTEgPC0gZmlsdGVyZWRfZGF0YTFbLXRyYWluX2luZGljZXMxLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTEgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCkludmVzdGlnYXRlIHBhY2thZ2UgZ2dwYXJ0eSAoZS5nLiBhdXRvcGxvdCBmdW5jdGlvbiwgYW5kIG1vcmUpLg0KDQpUTy1ETzogDQpDaG9vc2UgdGhlIGh5cGVycGFyYW1ldGVyIG10cnkgYmFzZWQgb24gdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcyAoSGFzdGllIGV0IGFsLiwgMjAwOSktDQoNCkhhc3RpZSwgVC4sIFRpYnNoaXJhbmksIFIuLCAmIEZyaWVkbWFuLCBKLiAoMjAwOSkuIFRoZSBlbGVtZW50cyBvZiBzdGF0aXN0aWNhbA0KbGVhcm5pbmc6IERhdGEgbWluaW5nLCBpbmZlcmVuY2UsIGFuZCBwcmVkaWN0aW9uLiBTcHJpbmdlciBTY2llbmNlICYNCkJ1c2luZXNzIE1lZGlhLg0KDQpNYXliZSBUT19ETzoNCldlIHZhcmlhdGVkIG50cmVlIGZyb20gNTAgdG8gODAwIGluIHN0ZXBzIG9mIDUwLCBsZWF2aW5nIG10cnkgY29uc3RhbnQgYXQgMi4gVGlzIHBhcmFtZXRlciB2YXJpYXRpb24gc2hvd2VkIHRoYXQgbnRyZWU9NTAwIHdhcyBvcHRpbWFsLCB3aGlsZSBoaWdoZXIgbnRyZWUgbGVkIHRvIG5vIGZ1cnRoZXIgbW9kZWwgaW1wcm92ZW1lbnQgKFN1cHBsZW1lbnRhcnkgRmlnLiBTMTApLiBTdWJzZXF1ZW50bHksIHRoZSBoeXBlcnBhcmFtZXRlciBtdHJ5IHdhcyB2YXJpZWQgZnJvbSAyIHRvIDggd2l0aCBjb25zdGFudCBudHJlZT01MDAuIEhlcmUsIG10cnk9MyBsZWQgdG8gdGhlIGJlc3QgcmVzdWx0cyBpbiBhbG1vc3QgYWxsIGNhc2VzIChTdXBwbGVtZW50YXJ5IEZpZy4gUzExKS4gQ29uc2VxdWVudGx5LCB3ZSBjaG9zZSBudHJlZT01MDAgYW5kIG10cnk9MyBmb3Igb3VyIG1haW4gYW5hbHlzaXMgYWNyb3NzIGFsbCBzdHVkeSBzaXRlcy4NCg0KYGBge3J9DQpyZl9jZm9yZXN0MSA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QxIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MSwgdGVzdF9kYXRhMSRFVU5JU2FfMSkNCmBgYA0KDQpTdXJyb2dhdGVUcmVlIC0tPiBkb2VzIG5vdCB3b3JrDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gRikgDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpgYGANCg0KYGBge3J9DQojIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gVCkgDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MSkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDFfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClRyZWUgVmlzdWFsaXphdGlvbg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgc2luZ2xlIGNvbmRpdGlvbmFsIGluZmVyZW5jZSB0cmVlIHVzaW5nIGN0cmVlDQpzaW5nbGVfdHJlZTEgPC0gY3RyZWUoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKyBORE1JX21pbiArDQogICAgICAgICAgICAgICAgICAgICAgIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsgRVZJX21pbiArIFNBVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LA0KICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExKQ0KDQojIFBsb3QgdGhlIHNpbmdsZSB0cmVlIHVzaW5nDQphdXRvcGxvdChzaW5nbGVfdHJlZTEpDQpgYGANCg0KIyMgQWxsIEdQUyBwb2ludHMgd2l0aGluIElRIHJhbmdlDQoNCkZpbHRlciB0aGUgZGF0YSB0byBnZXQgb25seSBHUFMtcG9pbnRzIHdpdGhpbiBJUSByYW5nZSBvZiBORFZJX21heCBhbmQgTkRNSV9taW4uDQoNCmBgYHtyfQ0KSVFfcmFuZ2VzIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIFExX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIDAuMjUsIG5hLnJtID0gVCksDQogICAgUTFfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgMC4yNSwgbmEucm0gPSBUKSwNCiAgICBRM19ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCAwLjc1LCBuYS5ybSA9IFQpLA0KICAgIFEzX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIDAuNzUsIG5hLnJtID0gVCkNCiAgICApDQoNCiMgSm9pbiB0aGUgSVEgcmFuZ2VzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRhdGENCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgbGVmdF9qb2luKElRX3JhbmdlcywgYnkgPSAiRVVOSVNhXzEiKQ0KDQojIEZpbHRlciByb3dzIHdpdGhpbiB0aGUgSVFSIHJhbmdlIGZvciBib3RoIHZhcmlhYmxlcw0KZmlsdGVyZWRfZGF0YTIgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZmlsdGVyKA0KICAgIChORFZJX21heCA+PSBRMV9ORFZJX21heCAmIE5EVklfbWF4IDw9IFEzX05EVklfbWF4KSAmDQogICAgKE5ETUlfbWluID49IFExX05ETUlfbWluICYgTkRNSV9taW4gPD0gUTNfTkRNSV9taW4pDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXMyIDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTIpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGEyKSkNCnRyYWluX2RhdGEyIDwtIGZpbHRlcmVkX2RhdGEyW3RyYWluX2luZGljZXMyLCBdDQp0ZXN0X2RhdGEyIDwtIGZpbHRlcmVkX2RhdGEyWy10cmFpbl9pbmRpY2VzMiwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEyICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3QyIDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDIgPC0gcHJlZGljdChyZl9jZm9yZXN0MiwgbmV3ZGF0YSA9IHRlc3RfZGF0YTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QyLCB0ZXN0X2RhdGEyJEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QyIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDIsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QyX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MikNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDJfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNCiMjIEFsbCBHUFMgcG9pbnRzIHdpdGhpbiBtZWFuICsvLSBTRA0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyB3aXRoaW4gbWVhbiArLy0gU0Qgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCm1lYW5fc2QgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbWVhbl9ORFZJX21heCA9IG1lYW4oYWxsX0dQU192YWxpZCRORFZJX21heCwgbmEucm0gPSBUKSwNCiAgICBtZWFuX05ETUlfbWluID0gbWVhbihhbGxfR1BTX3ZhbGlkJE5ETUlfbWluLCBuYS5ybSA9IFQpLA0KICAgIHNkX05EVklfbWF4ID0gc2QoYWxsX0dQU192YWxpZCRORFZJX21heCwgbmEucm0gPSBUKSwNCiAgICBzZF9ORE1JX21pbiA9IHNkKGFsbF9HUFNfdmFsaWQkTkRNSV9taW4sIG5hLnJtID0gVCkNCiAgICApDQoNCiMgSm9pbiB0aGUgSVEgcmFuZ2VzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRhdGENCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgbGVmdF9qb2luKG1lYW5fc2QsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBGaWx0ZXIgcm93cyB3aXRoaW4gdGhlIHNwZWNpZmllZCByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMNCmZpbHRlcmVkX2RhdGEzIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcigNCiAgICAoTkRWSV9tYXggPj0gKG1lYW5fTkRWSV9tYXggLSBzZF9ORFZJX21heCkgJiBORFZJX21heCA8PSAobWVhbl9ORFZJX21heCArIHNkX05EVklfbWF4KSkgJg0KICAgICAgKE5ETUlfbWluID49IChtZWFuX05ETUlfbWluIC0gc2RfTkRNSV9taW4pICYgTkRNSV9taW4gPD0gKG1lYW5fTkRNSV9taW4gKyBzZF9ORE1JX21pbikpDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXMzIDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTMpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGEzKSkNCnRyYWluX2RhdGEzIDwtIGZpbHRlcmVkX2RhdGEzW3RyYWluX2luZGljZXMzLCBdDQp0ZXN0X2RhdGEzIDwtIGZpbHRlcmVkX2RhdGEzWy10cmFpbl9pbmRpY2VzMywgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEzICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3QzIDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDMgPC0gcHJlZGljdChyZl9jZm9yZXN0MywgbmV3ZGF0YSA9IHRlc3RfZGF0YTMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QzLCB0ZXN0X2RhdGEzJEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QzIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDMsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QzX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MykNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDNfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNCiMjIEFsbCBHUFMgcG9pbnRzIGFib3ZlIHAyMCBhbmQgYmVsb3cgcDgwDQoNCkZpbHRlciB0aGUgZGF0YSB0byBnZXQgb25seSBHUFMtcG9pbnRzIGFib3ZlIHAyMCBhbmQgYmVsb3cgcDgwIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQojIEZpbHRlciByb3dzIGFib3ZlIHRoZSAyMHRoIHBlcmNlbnRpbGUgYW5kIGJlbG93IHRoZSA4MHRoIHBlcmNlbnRpbGUgZm9yIGJvdGggdmFyaWFibGVzDQpmaWx0ZXJlZF9kYXRhNCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBmaWx0ZXIoDQogICAgKE5EVklfbWF4ID49IHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggJiBORFZJX21heCA8PSBwZXJjZW50aWxlXzgwX05EVklfbWF4KSAmDQogICAgKE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gJiBORE1JX21pbiA8PSBwZXJjZW50aWxlXzgwX05ETUlfbWluKQ0KICAgICkgJT4lDQogIGZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzNCA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGE0KSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhNCkpDQp0cmFpbl9kYXRhNCA8LSBmaWx0ZXJlZF9kYXRhNFt0cmFpbl9pbmRpY2VzNCwgXQ0KdGVzdF9kYXRhNCA8LSBmaWx0ZXJlZF9kYXRhNFstdHJhaW5faW5kaWNlczQsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhNCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0NCA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGE0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3Q0IDwtIHByZWRpY3QocmZfY2ZvcmVzdDQsIG5ld2RhdGEgPSB0ZXN0X2RhdGE0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0NCwgdGVzdF9kYXRhNCRFVU5JU2FfMSkNCmBgYA0KDQojIEhFUkU6IENvbXBhcmUgUkYgMS00DQoNCiMgQ29yZGlsbGVyYSBkYXRhDQoNCmBgYHtyfQ0KQWxwaW5lR3Jhc3NsYW5kc19pbmRpY2VzIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL0FscGluZUdyYXNzbGFuZHMvQWxwaW5lR3Jhc3NsYW5kX1NlbnRpbmVsX1Bsb3RfQWxseWVhcl9BbGxtZXRyaWNzLmNzdiIpDQpBbHBpbmVHcmFzc2xhbmRzX3BoZW4gPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvQWxwaW5lR3Jhc3NsYW5kcy9BbHBpbmVHcmFzc2xhbmRzX1BoZW5vbG9neV9TT1NfRU9TX1BlYWtfTkRWSV9BbXBsaXR1ZGUuY3N2IikNCkFscGluZUdyYXNzbGFuZHNfQ0ggPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvQWxwaW5lR3Jhc3NsYW5kcy9BbHBpbmVHcmFzc2xhbmRzX0Nhbm9weUhlaWdodF8xbS5jc3YiKQ0KVmVnZXRhdGlvblR5cGVzX2luZGljZXMgPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvVmVnZXRhdGlvblR5cGVzL1ZlZ2V0YXRpb25UeXBlc19TZW50aW5lbF9QbG90X0FsbFllYXJfQWxsbWV0cmljcy5jc3YiKQ0KVmVnZXRhdGlvblR5cGVzX3BoZW4gPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvVmVnZXRhdGlvblR5cGVzL1ZlZ2V0YXRpb25UeXBlc19QaGVub2xvZ3lfU09TX0VPU19QZWFrX05EVklfQW1wbGl0dWRlLmNzdiIpDQpWZWdldGF0aW9uVHlwZXNfQ0ggPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvVmVnZXRhdGlvblR5cGVzL1ZlZ2V0YXRpb25UeXBlc19DYW5vcHlIZWlnaHRfMW0uY3N2IikNCmBgYA0KDQpgYGB7cn0NCkFscGluZUdyYXNzbGFuZHMgPC0gQWxwaW5lR3Jhc3NsYW5kc19pbmRpY2VzICU+JQ0KICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbywgLUxvY2FsaWRhZCkgJT4lDQogIHJlbmFtZShIw6FiaXRhdCA9ICJI77+9Yml0YXQiKSAlPiUgDQogIGZ1bGxfam9pbihBbHBpbmVHcmFzc2xhbmRzX3BoZW4gICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbywgLUxvY2FsaWRhZCkgJT4lDQogICAgICAgICAgICAgIHJlbmFtZShIw6FiaXRhdCA9ICJI77+9Yml0YXQiKSkgJT4lDQogIGZ1bGxfam9pbihBbHBpbmVHcmFzc2xhbmRzX0NIICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8sIC1Mb2NhbGlkYWQpKSAlPiUNCiAgc2VsZWN0KC1EYXRlX195ZWFyLCAtIGBQcmVjaXNp77+9bmApICU+JQ0KICBtdXRhdGUoREFURSA9IHltZChEQVRFKSkgJT4lDQogIHJlbmFtZShJRCA9ICJSZWxldmVfbnVtIikgJT4lDQogIG11dGF0ZShJRCA9IGFzLmNoYXJhY3RlcihJRCkpICU+JQ0KICBtdXRhdGUobGF5ZXIgPSAiQWxwaW5lR3Jhc3NsYW5kcyIpDQpgYGANCg0KYGBge3J9DQpWZWdldGF0aW9uVHlwZXMgPC0gVmVnZXRhdGlvblR5cGVzX2luZGljZXMgJT4lDQogIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvKSAlPiUNCiAgZnVsbF9qb2luKFZlZ2V0YXRpb25UeXBlc19waGVuICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8pKSAlPiUNCiAgZnVsbF9qb2luKFZlZ2V0YXRpb25UeXBlc19DSCAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvKSkgJT4lDQogIHJlbmFtZShIw6FiaXRhdCA9ICJUWVBFIikgJT4lDQogIG11dGF0ZShsYXllciA9ICJWZWdldGF0aW9uVHlwZXMiKQ0KYGBgDQoNCk1lcmdlIGJvdGggZGF0YXNldHM6DQoNCmBgYHtyfQ0KY29yZGlsbGVyYSA8LSBiaW5kX3Jvd3MoDQogIEFscGluZUdyYXNzbGFuZHMgJT4lIHNlbGVjdChEQVRFLCBJRCwgc3RhcnRzX3dpdGgoIk5ETUkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0c193aXRoKCJORFZJIiksIEjDoWJpdGF0LCAiRU9TX0RPWSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUGVha19ET1kiLCAiU09TX0RPWSIsICJTZWFzb25fTGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYW5vcHlfaGVpZ2h0IiwgImxheWVyIiksDQogIFZlZ2V0YXRpb25UeXBlcyAlPiUgc2VsZWN0KERBVEUsIElELCBzdGFydHNfd2l0aCgiTkRNSSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRzX3dpdGgoIk5EVkkiKSwgSMOhYml0YXQsICJFT1NfRE9ZIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrX0RPWSIsICJTT1NfRE9ZIiwgIlNlYXNvbl9MZW5ndGgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNhbm9weV9oZWlnaHQiLCAibGF5ZXIiKQ0KICApICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBjYXNlX3doZW4oDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiUGFzdGl6YWx8Q2VydnVuYWx8Z3Jhc3NsYW5kfG1lYWRvdyIpIH4gIlIiLA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgImZvcmVzdCIpIH4gIlQiLA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgIlNjcnVifHNjcnVifFNocnVibGFuZHxzaHJ1YmxhbmR8c2hydWJ8SGVhdGhsYW5kIikgfiAiUyIsDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiU3VlbG98U2NyZWV8c2NyZWV8Y2xpZmYiKSB+ICJVIiwNCiAgICBIw6FiaXRhdCA9IGlzLm5hKEjDoWJpdGF0KSB+ICJSIiwNCiAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXyksDQogICAgRVVOSVNhXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2FfMSA9PSAiUiIgfiAiR3Jhc3NsYW5kcyIsDQogICAgICBFVU5JU2FfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNhXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNhXzEgPT0gIlUiIH4gIklubGFuZCBoYWJpdGF0cyB3aXRoIG5vIG9yIGxpdHRsZSBzb2lsIikNCiAgICApDQpgYGANCg0KIyMgTWF4LiBhbmQgbWluLiBORFZJLCBORE1JDQoNCmBgYHtyfQ0KcGxvdF9kaXN0cl9ORFZJX21heF9jb3JkaSA8LSBkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRWSV9tYXgiLCAiTkRWSSBtYXgiKQ0KcGxvdF9kaXN0cl9ORFZJX21pbl9jb3JkaSA8LSBkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRWSV9taW4iLCAiTkRWSSBtaW4iKQ0KcGxvdF9kaXN0cl9ORE1JX21heF9jb3JkaSA8LSBkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRNSV9tYXgiLCAiTkRNSSBtYXgiKQ0KcGxvdF9kaXN0cl9ORE1JX21pbl9jb3JkaSA8LSBkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkRNSV9taW4iLCAiTkRNSSBtaW4iKQ0KcGxvdF9kaXN0cl9ORFZJX21heF9jb3JkaQ0KcGxvdF9kaXN0cl9ORFZJX21pbl9jb3JkaQ0KcGxvdF9kaXN0cl9ORE1JX21heF9jb3JkaQ0KcGxvdF9kaXN0cl9ORE1JX21pbl9jb3JkaQ0KYGBgDQoNCiMgT0xEIGZyb20gaGVyZQ0KDQojIyMgUGxvdHMgTkRWSSBhbmQgTkRNSQ0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gY29yZGkgJT4lIGZpbHRlcighaXMubmEoTkRWSV9tYXgpKSAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBmaWx0ZXIoRVVOSVNfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSksDQogICAgICAgYWVzKHggPSBFVU5JU18xLCB5ID0gTkRWSV9tYXgsIGZpbGwgPSBFVU5JU18xKSkgKw0KICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCkgKw0KICBnZW9tX3BvaW50KGFlcyh5ID0gTkRWSV9tYXgsIGNvbG9yID0gRVVOSVNfMSksDQogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDMsDQogICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogIHN0YXRfc3VtbWFyeShmdW4ueT1tZWFuLCBnZW9tPSJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAzKSArDQogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkNCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJjb3JkaV9ORFZJX21heC50aWZmIiksDQogICAgICAgd2lkdGggPSAyMSwgaGVpZ2h0ID0gMjkuNywgdW5pdHMgPSAiY20iLCBkcGkgPSAzMDApDQpnZ3Bsb3QoZGF0YSA9IGNvcmRpICU+JSBmaWx0ZXIoIWlzLm5hKE5ETUlfbWF4KSkgJT4lDQogICAgICAgICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICAgICAgICAgZmlsdGVyKEVVTklTXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgIGFlcyh4ID0gRVVOSVNfMSwgeSA9IE5ETUlfbWF4LCBmaWxsID0gRVVOSVNfMSkpICsNCiAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgpICsNCiAgZ2VvbV9wb2ludChhZXMoeSA9IE5ETUlfbWF4LCBjb2xvciA9IEVVTklTXzEpLA0KICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAzLA0KICAgICAgICAgICAgIGFscGhhID0gMC41KSArDQogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLnk9bWVhbiwgZ2VvbT0icG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMykgKw0KICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKyB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpDQpnZ3NhdmUoaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAiY29yZGlfTkRNSV9tYXgudGlmZiIpLA0KICAgICAgIHdpZHRoID0gMjEsIGhlaWdodCA9IDI5LjcsIHVuaXRzID0gImNtIiwgZHBpID0gMzAwKQ0KYGBgDQoNCiMjIyBNYXBzDQoNCmBgYHtyfQ0KIyBMb2FkIHdvcmxkIGJvdW5kYXJpZXMNCndvcmxkIDwtIG5lX2NvdW50cmllcyhzY2FsZSA9ICJtZWRpdW0iLCByZXR1cm5jbGFzcyA9ICJzZiIpDQpgYGANCg0KYGBge3J9DQojIENhbGN1bGF0ZSB0aGUgZXh0ZW50IG9mIHRoZSBwb2ludHMNCnBvaW50c19jb3JkaV9leHRlbnQgPC0gY29yZGkgJT4lIGZpbHRlcihFVU5JU18xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgc3VtbWFyaXNlKGxvbl9taW4gPSBtaW4obG9uZ2l0dWRlLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbG9uX21heCA9IG1heChsb25naXR1ZGUsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWluID0gbWluKGxhdGl0dWRlLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21heCA9IG1heChsYXRpdHVkZSwgbmEucm0gPSBUUlVFKSkNCg0KIyBBZGQgcGFkZGluZyB0byB0aGUgZXh0ZW50IChhZGp1c3QgYXMgbmVlZGVkKQ0KcGFkZGluZyA8LSAzICAjIEFkanVzdCBwYWRkaW5nIHRvIHlvdXIgcHJlZmVyZW5jZQ0KeF9saW1pdHMgPC0gYyhwb2ludHNfY29yZGlfZXh0ZW50JGxvbl9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfY29yZGlfZXh0ZW50JGxvbl9tYXggKyBwYWRkaW5nKQ0KeV9saW1pdHMgPC0gYyhwb2ludHNfY29yZGlfZXh0ZW50JGxhdF9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfY29yZGlfZXh0ZW50JGxhdF9tYXggKyBwYWRkaW5nKQ0KDQojIENyZWF0ZSB0aGUgem9vbWVkIG1hcA0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB3b3JsZCwgZmlsbCA9ICJsaWdodGJsdWUiLCBjb2xvciA9ICJncmF5IikgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBjb3JkaSAlPiUgZmlsdGVyKEVVTklTXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgICAgICAgIGFlcyh4ID0gbG9uZ2l0dWRlLCB5ID0gbGF0aXR1ZGUsIGNvbG9yID0gRVVOSVNfMSksDQogICAgICAgICAgICAgc2l6ZSA9IDEpICsNCiAgY29vcmRfc2YoeGxpbSA9IHhfbGltaXRzLCB5bGltID0geV9saW1pdHMpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgUG9pbnRzIGRpZmZlcmVudGlhbCBHUFMNCg0KIyMjIE1hcHMNCg0KYGBge3J9DQojIENhbGN1bGF0ZSB0aGUgZXh0ZW50IG9mIHRoZSBwb2ludHMNCnBvaW50c19HUFNfZXh0ZW50IDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiKSAlPiUNCiAgc3VtbWFyaXNlKGxvbl9taW4gPSBtaW4oTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsb25fbWF4ID0gbWF4KExvbl91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21pbiA9IG1pbihMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxhdF9tYXggPSBtYXgoTGF0X3VwZGF0ZWQsIG5hLnJtID0gVFJVRSkpDQoNCiMgQWRkIHBhZGRpbmcgdG8gdGhlIGV4dGVudCAoYWRqdXN0IGFzIG5lZWRlZCkNCnBhZGRpbmcgPC0gMiAgIyBBZGp1c3QgcGFkZGluZyB0byB5b3VyIHByZWZlcmVuY2UNCnhfbGltaXRzIDwtIGMocG9pbnRzX0dQU19leHRlbnQkbG9uX21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19HUFNfZXh0ZW50JGxvbl9tYXggKyBwYWRkaW5nKQ0KeV9saW1pdHMgPC0gYyhwb2ludHNfR1BTX2V4dGVudCRsYXRfbWluIC0gcGFkZGluZywNCiAgICAgICAgICAgICAgcG9pbnRzX0dQU19leHRlbnQkbGF0X21heCArIHBhZGRpbmcpDQoNCiMgQ3JlYXRlIHRoZSB6b29tZWQgbWFwDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHdvcmxkLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImdyYXkiKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICAgICAgICAgIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICAgICAgICAgICAgICAgZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiKSwNCiAgICAgICAgICAgICBhZXMoeCA9IExvbl91cGRhdGVkLCB5ID0gTGF0X3VwZGF0ZWQsIGNvbG9yID0gRVVOSVNhXzEpLA0KICAgICAgICAgICAgIHNpemUgPSAxKSArDQogIGNvb3JkX3NmKHhsaW0gPSB4X2xpbWl0cywgeWxpbSA9IHlfbGltaXRzKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCk51bWJlciBvZiBkaWZmZXJlbnRpYWwgR1BTIHBvaW50cyBieSBDb3VudHJ5Og0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiKSAlPiUNCiAgY291bnQoQ291bnRyeSkNCmBgYA0KDQojIyBQb2ludHMgUmVTdXJ2ZXkNCg0KIyMjIE1hcHMNCg0KYGBge3J9DQojIENhbGN1bGF0ZSB0aGUgZXh0ZW50IG9mIHRoZSBwb2ludHMNCnBvaW50c19yZXN1cnZleV9leHRlbnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICBzdW1tYXJpc2UobG9uX21pbiA9IG1pbihMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxvbl9tYXggPSBtYXgoTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWluID0gbWluKExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21heCA9IG1heChMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSkNCg0KIyBBZGQgcGFkZGluZyB0byB0aGUgZXh0ZW50IChhZGp1c3QgYXMgbmVlZGVkKQ0KcGFkZGluZyA8LSAyICAjIEFkanVzdCBwYWRkaW5nIHRvIHlvdXIgcHJlZmVyZW5jZQ0KeF9saW1pdHMgPC0gYyhwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxvbl9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxvbl9tYXggKyBwYWRkaW5nKQ0KeV9saW1pdHMgPC0gYyhwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxhdF9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxhdF9tYXggKyBwYWRkaW5nKQ0KDQojIENyZWF0ZSB0aGUgem9vbWVkIG1hcA0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB3b3JsZCwgZmlsbCA9ICJsaWdodGJsdWUiLCBjb2xvciA9ICJncmF5IikgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgICAgICAgICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSwNCiAgICAgICAgICAgICBhZXMoeCA9IExvbl91cGRhdGVkLCB5ID0gTGF0X3VwZGF0ZWQsIGNvbG9yID0gRVVOSVNhXzEpLA0KICAgICAgICAgICAgIHNpemUgPSAxKSArDQogIGNvb3JkX3NmKHhsaW0gPSB4X2xpbWl0cywgeWxpbSA9IHlfbGltaXRzKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCk51bWJlciBvZiBSZVN1cnZleSBwb2ludHMgYnkgQ291bnRyeToNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogIGNvdW50KENvdW50cnkpDQpgYGANCg0KIyMgSm9pbg0KDQpgYGB7cn0NCnBvaW50c19oYWxsZSA8LSBiaW5kX3Jvd3MoDQogIGNvcmRpICU+JSBmaWx0ZXIoRVVOSVNfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgc2VsZWN0KGxvbmdpdHVkZSwgbGF0aXR1ZGUsIE5EVklfbWF4LCBORE1JX21heCwgRVVOSVNfMSkgJT4lDQogICAgcmVuYW1lKEVVTklTID0gRVVOSVNfMSkgJT4lDQogICAgbXV0YXRlKHBvaW50X3R5cGUgPSAiQ29yZGlsbGVyYSIpLA0KICBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICAgIGZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIikgJT4lDQogICAgc2VsZWN0KExvbl91cGRhdGVkLCBMYXRfdXBkYXRlZCwgTkRWSV9tYXgsIE5ETUlfbWF4LCBFVU5JU2FfMSkgJT4lDQogICAgcmVuYW1lKGxvbmdpdHVkZSA9IExvbl91cGRhdGVkLCBsYXRpdHVkZSA9IExhdF91cGRhdGVkLA0KICAgICAgICAgICBFVU5JUyA9IEVVTklTYV8xKSAlPiUNCiAgICBtdXRhdGUocG9pbnRfdHlwZSA9ICJSZVN1cnZleSBkaWZmZXJlbnRpYWwgR1BTIiksDQogIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogICAgc2VsZWN0KExvbl91cGRhdGVkLCBMYXRfdXBkYXRlZCwgTkRWSV9tYXgsIE5ETUlfbWF4LCBFVU5JU2FfMSkgJT4lDQogICAgcmVuYW1lKGxvbmdpdHVkZSA9IExvbl91cGRhdGVkLCBsYXRpdHVkZSA9IExhdF91cGRhdGVkLA0KICAgICAgICAgICBFVU5JUyA9IEVVTklTYV8xKSAlPiUNCiAgICBtdXRhdGUocG9pbnRfdHlwZSA9ICJSZVN1cnZleSIpDQopDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHBvaW50c19oYWxsZSwNCiAgICAgICBhZXMoeCA9IHBvaW50X3R5cGUsIHkgPSBORFZJX21heCwgZmlsbCA9IHBvaW50X3R5cGUpKSArDQogIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44KSArDQogIGdlb21fcG9pbnQoYWVzKHkgPSBORFZJX21heCwgY29sb3IgPSBwb2ludF90eXBlKSwNCiAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMywNCiAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55PW1lYW4sIGdlb209InBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDMpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKSArDQogIGZhY2V0X3dyYXAofiBFVU5JUykNCmBgYA0KDQojIFNlc3Npb24gaW5mbw0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=